第五课之初步认识react

课程回顾

开发环境和生产环境

开发环境和生产环境有很多通用的代码,可以将通用的代码提取出来,例如loaders,output等,所以可以形成三个文件,webpack.prod.config.js、webpack.dev.config.js、webpack.common.config.js,分离出来如何将他们分别合并,这时候就需要使用到webpack-merge,进行配置合并

课程内容

  • 1.课程回顾
  • 2.react介绍
  • 3.react安装
  • 4.生命周期、事件、数据流讲解
  • 5.父子组件传值

1.什么是React

React是Facebook推出的一个JavaScript库,它的口号就是“用来创建用户界面的JavaScript库”,所以它只是和用户界面打交道,可以把它看成MVC中的V(视图)层。

为什么使用react

  • 虚拟dom效率性能高,不需要操作dom(频繁操作dom很消耗性能)
  • 使用组件化开发方式,高复用、维护容易,逻辑清晰
  • 技术成熟,社区完善,插件齐全,适用于大型Web项目(生态系统健全),由Facebook专门的团队维护,技术支持可靠,是现在最主流的技术之一
  • 全局单向数据流,数据流向清晰,大型项目不在话下

组件化

  • 每一个ReactJS文件都是一个组件,含视图、逻辑操作、数据
  • 复用、封装性强

单向数据流

  • 子组件对于父组件传递过来的数据是【只读】的,不能直接更新父组件数据、这样组件更加简单易把握、只会在父组件修改数据,追查问题的时候可以跟快捷
  • 顶层组件某个props改变会递归遍历整棵组件树,重新渲染使用这个属性的插件

虚拟dom

  • React的设计中,开发者基本上无需操纵实际的DOM节点,每个React组件都是用Virtual DOM渲染的。
  • 用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树
  • 新的虚拟 DOM 与原来的虚拟 DOM进行比对时,它会进行同层比较,即相同的节点层进行比较,如果不同则直接将原始虚拟 DOM 中该节点层及以下的节点全部删除,重新生成新的虚拟 DOM 节点,而不会继续向下比对
jsx
  1. JSX 是 JavaScript 语言的一种语法扩展,长得像 HTML,但并不是 HTML。
  2. React.js 可以用 JSX 来描述你的组件长什么样的。
  3. JSX 在编译的时候会变成相应的 JavaScript 对象描述。
  4. react-dom 负责把这个用来描述 UI 信息的 JavaScript 对象变成 DOM 元素,并且渲染到页面上。
// 第一步:jsx代码
<button className="btn btn-blue">
    <em>Confirm</em>
</button>
// 第二步解析成对象
{
  type: 'button',
  props: {
    className: 'btn btn-blue',
    children: {
      type: 'em',
      props: {
        children: 'Confirm'
      }
    }
  }
}
// 第三步:根据对象创建标签
// 参数1:元素名称  
// 参数2:元素属性对象(null表示无)  
// 参数3:当前元素的子元素string||createElement() 的返回值

React.createElement("button", {
      className: "btn btn-blue"
    },React.createElement("em", null, "Confirm"))
  
// 第四步:渲染
ReactDOM.render(dom, document.getElementById('app'))
key
  • 例如:在遍历数据时,推荐在组件中使用 key 属性

  • 循环需要加key:你在 JSX 模板中遍历 state 中某个数据时,为什么不加 key 值浏览器会报警告,这是因为你不再遍历的每条数据加上 key 值,更改 state 中那条数据的值,生成虚拟 DOM 后,React 就不知道原始遍历的数据和这次更新后遍历的数据一一对应的关系,就会再次重新渲染,而加上 key 值,它则能迅速比对出有差异的部分进行部分的更新。

  • 为什么不建议用 index 作为 key 值:因为当你插入、 删除中间的数据时,从改变的那个数据开始,后续每个数据的 index 值就会变,从而就导致了每个数据的 key 值相应变化了,这样依旧会引起大规模渲染,这就是其中的原因

  • 流程图

虚拟dom、jsx

  • 从 JSX 到页面到底经过了什么样的过程:

2.react安装

// 第一步:安装react react-dom,以及jsx编译
npm i -D react react-dom @babel/preset-react
// 第二步:.babelrc添加
"presets": [
  "@babel/env",
  "@babel/react"
],

// 第三步:添加react.js
// 1. 导入 react
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
// 由于react实现机制,组件名称必须要大写
class MyComponet extends Component {
   constructor(props) {
        super(props);
        this.state = {};
    }
    render() {
        return (
            // 此处注释的写法 
            <button className="btn btn-blue">
                <em>Confirm</em>
            </button>
        )
    }
}
// 第四步:渲染
// 参数1:虚拟dom对象  参数2:dom对象表示渲染到哪个元素内 参数3:回调函数
ReactDOM.render(<MyComponet />, document.getElementById('root'))

// 第五步:修改入口文件
entry: './src/js/react.js',  //指定打包的入口文件

  • 组件名称命名用大驼峰
  • 如果在 JSX 中给元素添加类, 需要使用 className 代替 class
  • 在 JSX 中可以直接使用 JS代码,直接在 JSX 中通过 {} 中间写 JS代码即可

生命周期

  1. 挂载卸载过程
  • constructor():React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。
  • componentWillMount():它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时
  • componentDidMount():组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
  • componentWillUnmount ():在此处完成组件的卸载和数据的销毁。(移除计时器)
  1. 更新过程
  • componentWillReceiveProps (nextProps):接受一个参数nextProps,通过对比nextProps和this.props是否改变,判断要做的操作,只要父组件改变都会造成子组件componentWillReceiveProps接收,也会增加组件的重绘次数,浪费性能,写法不好容易死循环
  • shouldComponentUpdate(nextProps,nextState):主要用于性能优化(阻止组件渲染),因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
  • componentWillUpdate (nextProps,nextState):shouldComponentUpdate返回true以后,组件进入重新渲染的流程,进入componentWillUpdate,这里同样可以拿到nextProps和nextState。
  • componentDidUpdate(prevProps,prevState):react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state
  • render():render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染
  1. React新增的生命周期
  • getDerivedStateFromProps(nextProps, prevState):代替componentWillReceiveProps(),获取props并修改state
  • getSnapshotBeforeUpdate(prevProps, prevState):一般的用法就是获取更新前的DOM
  1. React17版本废弃的生命周期
  • componentWillMount
  • componentWillReceiveProps(可以用getDerivedStateFromProps和componentDidUpdate搭配使用替代)
  • componentWillUpdate

修改后的生命周期

// 之前
componentWillReceiveProps(nextProps) {
  if (nextProps.isLogin !== this.props.isLogin) {
    this.setState({ 
      isLogin: nextProps.isLogin,   
    });
    this.handleClose();
  }
}
// 现在
static getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.isLogin !== prevState.isLogin) {
    return {
      isLogin: nextProps.isLogin,
    };
  }
  return null;
}

componentDidUpdate(prevProps, prevState) {
  if (prevState.isLogin !== this.state.isLogin) {
    this.handleClose();
  }
}

使用方式

class MyComponet extends Component {
   constructor(props) {
        super(props);
        this.state = {};
    }
    // 日常使用的生命周期
    // 新增的生命周期
    static getDerivedStateFromProps(nextProps, prevState) {
        console.log('判断前后两个 props     是否相同------getDerivedStateFromProps')
        return null;
    }
    componentDidUpdate() {
        console.log('数据已经更新---------componentDidUpdate')
    }
    
    // 组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
    componentDidMount() {
        console.log('组件加载后--------componentDidMount')
    }
    // 即将废弃
    // componentWillReceiveProps(nextProps) {
    //     console.log('接收props--------componentWillReceiveProps')
    // }
    // 性能优化
    // 你可以通过这个方法控制组件是否重新渲染。如果返回 false     组件就不会重新渲染。
    shouldComponentUpdate(nextProps, nextState){
         // 值没有发生过变化,不需要重新触发render
         if(nextState.Number == this.state.Number){
            return false
         }
    }
    render() {
        return (
            // 此处注释的写法 
            <button className="btn btn-blue">
                <em>Confirm</em>
            </button>
        )
    }
}

render(不要在render里setState)

  1. 一个组件类必须要实现一个 render 方法。
  2. 这个 render 方法必须要返回一个 JSX 元素。
  3. 必须要用一个外层的 JSX 元素把所有内容包裹起来。
// 错误
render () {
  return (
    <div>第一个</div>
    <div>第二个</div>
  )
}

// 正确
render () {
  return (
    <div>
      <div>第一个</div>
      <div>第二个</div>
    </div>
  )
}

react事件

// 绑定事件,this指向会改变所以我们通过代码控制不改变this的指向
// 写法一:构造函数里绑定事件,效率高
constructor(props) {
    super(props);
    this.state = {};
    this.showInfo = this.showInfo.bind(this);
}
showInfo() {
    console.log(2312321)
}
<button className="btn btn-blue" onClick={this.showInfo}>
    <em>Confirm</em>
</button>

// 写法二:使用bind this
showInfo() {
    console.log(2312321)
}
<button className="btn btn-blue" onClick={this.showInfo.bind(this)}>
    <em>Confirm</em>
</button>

// 写法三:方法使用箭头函数,this指向不会改变,简单易用,效率高,建议使用
showInfo=()=> {
    console.log(2312321)
}
<button className="btn btn-blue" onClick={this.showInfo}>
    <em>Confirm</em>
</button>

// 写法四:在标签上调用函数,每次渲染会调用,不建议使用
<button className="btn btn-blue" onClick={()=>this.showInfo()}>
    <em>Confirm</em>
</button>

数据讲解

1.state:内部定义,它只是用来控制这个组件本身自己的状态,页面渲染通过setState进行完成。

2.setState:

  • 当我们调用这个函数的时候,React.js 会更新组件的状态 state ,并且重新调用 render 方法,然后再把 render 方法所渲染的最新的内容显示到页面上
  • React.js为了批次与效能并不会马上修改state。而是把这个对象放到一个更新队列里面,稍后才会从队列当中把新的状态提取出来合并到 state 当中,然后再触发组件更新。
// 想要即时获得改变后的state
// 方案一:使用回调函数
this.setState({ val: this.state.val + 1 }, () => {
  console.log(this.state.val);     
})

// 方案二:setTimeout是异步方法,react无法直到开发者异步方法中想要渲染的顺序,所以在异步方法中调用setTimeout,react会即时渲染,不会使用批量更新
setTimeout(_ => {
    this.setState({
        num: ++num
    })
    console.log(this.state.num);
}, 0)

3.props:外部传入,包括父子组件之间的通信,全局数据流的传递

  • 父子组件之间的通信,子组件无法直接修改父组件的props,通过this.props接收
  • 全局数据流的传递(dva)

image

父子组件传值

  • 父组件使用子组件并添加属性进行传值
  • 子组件接收属性或者方法
  • 实现父组件state的使用,以及子组件props的使用
案例
// 第一步:添加一个子组件ChildCom.js,组件首字母大写
import React, { Component } from 'react'

export default class ChildCom extends Component {
    constructor(props) {
        super(props);
        this.state = {
        };
    }
    clickBtn = () => {
        // 使用父组件的方法
        this.props.addChildNum();
    }
    render() {
        // 使用父组件的值
        const { childNum } = this.props;
        return (
            <div>
                <div>子组件值:{childNum}</div>
                <button onClick={this.clickBtn}>子组件点击调用父组件方法</button>
            </div>
        )
    }
}

// 第二步父组件添加
// 1. 导入 react
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
// 导入子组件
import ChildCom from './ChildCom';
// 2. 创建 虚拟DOM
// 参数1:元素名称  参数2:元素属性对象(null表示无)  参数3:当前元素的子元素string||createElement() 的返回值
class MyComponet extends Component {
    constructor(props) {
        super(props);
        this.state = {
            num: 1,
            childNum: 1,
        };
    }
    ...
    addNum = () => {
        let { num } = this.state;
        this.setState({
            num: ++num
        })
    }
    addChildNum = () => {
        let { childNum } = this.state;
        this.setState({
            childNum: ++childNum
        })
    }
    render() {
        const { num, childNum } = this.state;
        return (
            <div>
                <div>父组件值:{num}</div>
                <button className="btn btn-blue" onClick={this.addNum}>
                    <em>Confirm</em>
                </button>
                <ChildCom childNum={childNum} addChildNum={this.addChildNum} />
            </div>

        )
    }
}

ref

不借助插件的情况下,react中如何获取获取dom节点

方式一

使用原生js,需要在componentDidMount(组件渲染完成后获取)

// 原生js获取获取dom节点
document.getElementById('');
~
~
~
方式二

react提供的一个特殊的属性ref,可以绑定到render()输出的任何组件上,这个特殊的属性允许你引用 render() 返回的相应的dom节点

// 第一步:添加ref属性
<input value={num} ref="myIpt" readOnly />

// 第二步:获取节点
getDom = () => {
    var dom = this.refs.myIpt;
    dom.style.background='red';
    dom.focus();
}

父组件如何获取子组件的数据(props,state,event)

// 第一步:父组件使用并子组件添加onRef={(child) => this.child = child}(可以取别的名字)
// 作用是设置当前父组件的this.child等于返回的对象
 <ChildCom childNum={childNum} addChildNum={this.addChildNum} onRef={(child) => this.child = child} />

// 第二步:子组件ChildCom添加
componentDidMount() {
    // 将child传递给this.props.onRef()方法
    this.props.onRef && this.props.onRef(this);
}

// 第三步父组件使用
console.log(this.child); // ChildCom {props: {…}, context: {…}, refs: {…}, updater: {…}, clickBtn: ƒ, …}
  • 百分之99的情况在不使用ref的情况下,react都能进行完成,频繁的dom操作是比较消耗性能的,所以在任何不需要ref去操作页面数据的的情况下,都不要使用ref去操作dom
原文地址:https://www.cnblogs.com/Hsong/p/13977174.html