React学习笔记——生命周期

663 阅读4分钟

类组件声明周期

image.png

React生命周期有两个重要阶段,render阶段和commit阶段。

React 在调和( render )阶段会深度遍历 React fiber 树,目的就是发现不同( diff ),不同的地方就是接下来需要更新的地方,对于变化的组件,就会执行 render 函数。在一次调和过程完毕之后,就到了commit 阶段,commit 阶段会创建修改真实的 DOM 节点。

从图中我们可以发现,生命周期的执行分为三大阶段,组件初始化组件更新 , 组件销毁

React 的大部分生命周期的执行,都在 mountClassInstance 和updateClassInstance 这两个方法中执行,mount (初始化渲染) 和 update (更新)两个方向上。

初始化阶段 4步

1. constructor 执行

实例化React组件,初始化。

2. getDerivedStateFromProps 执行

传入 props ,state 。 返回值将和之前的 state 合并,作为新的 state ,传递给组件实例使用。

3. componentWillMount 执行

如果存在 getDerivedStateFromProps 和 getSnapshotBeforeUpdate 就不会执行生命周期componentWillMount

  • componentWillReceiveProps 可以用来监听父组件是否执行 render 。
  • componentWillReceiveProps 可以用来接受 props 改变,组件可以根据props改变,来决定是否更新 state ,因为可以访问到 this , 所以可以在异步成功回调(接口请求数据)改变 state 。这个是 getDerivedStateFromProps 不能实现的。

4. render 函数执行
5. componentDidMount执行

一旦 React 调和完所有的 fiber 节点,就会到 commit 阶段,在组件初始化 commit 阶段,会调用 componentDidMount 生命周期。

  • 可以做一些关于 DOM 操作,比如基于 DOM 的事件监听器。
  • 对于初始化向服务器请求数据,渲染视图,这个生命周期也是蛮合适的。

更新阶段 6步

1. 执行生命周期 componentWillReceiveProps

首先判断 getDerivedStateFromProps 生命周期是否存在,如果不存在就执行componentWillReceiveProps生命周期。传入该生命周期两个参数,分别是 newProps 和 nextContext 。

2. 执行生命周期 getDerivedStateFromProps

getDerivedStateFromProps(nextProps,prevState)

3. 执行生命周期 shouldComponentUpdate

shouldComponentUpdate(newProps,newState,nextContext){}

4. 执行生命周期 componentWillUpdate

  • 获取组件更新之前的状态。比如 DOM 元素位置等。

5. 执行 render 函数

所谓 render 函数,就是 jsx 的各个元素被 React.createElement 创建成 React element 对象的形式。一次 render 的过程,就是创建 React.element 元素的过程。

6. 执行 getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps,preState){ return ...}

getSnapshotBeforeUpdate 的执行也是在 commit 阶段,commit 阶段细分为 before Mutation( DOM 修改前),Mutation ( DOM 修改),Layout( DOM 修改后) 三个阶段,getSnapshotBeforeUpdate 发生在before Mutation 阶段,生命周期的返回值,将作为第三个参数__reactInternalSnapshotBeforeUpdate 传递给 componentDidUpdate。

  • getSnapshotBeforeUpdate 这个生命周期意义就是配合componentDidUpdate 一起使用,计算形成一个 snapShot 传递给 componentDidUpdate 。保存一次更新前的信息。

7. 执行 componentDidUpdate

componentDidUpdate(prevProps, prevState, snapshot){}

销毁阶段 1步

执行生命周期 componentWillUnmount 在commit阶段调用

  • 清除延时器,定时器。
  • 一些基于 DOM 的操作,比如事件监听器。

函数组件

useEffect 和 useLayoutEffect

对于 useEffect 执行, React 处理逻辑是采用异步调用 ,对于每一个 effect 的 callback, React 会向 setTimeout回调函数一样,放入任务队列,等到主线程任务完成,DOM 更新,js 执行完成,视图绘制完毕,才执行。所以 effect 回调函数不会阻塞浏览器绘制视图。、

useLayoutEffect 和 useEffect 不同的地方是采用了同步执行,useLayoutEffect 是在DOM 绘制之前,这样可以方便修改 DOM ,useLayoutEffect callback 中代码执行会阻塞浏览器绘制。

一句话概括如何选择 useEffect 和 useLayoutEffect :修改 DOM ,改变布局就用 useLayoutEffect ,其他情况就用 useEffect 。

类组件和函数组件差别

useEffect 对 React 执行栈来看是异步执行的,而 componentDidMount / componentDidUpdate 是同步执行的,useEffect代码不会阻塞浏览器绘制。在时机上 ,componentDidMount / componentDidUpdate 和 useLayoutEffect 更类似。

函数组件生命周期替代方案

这个直接上代码更明显

function FunctionLifecycle(props){
    const [ num , setNum ] = useState(0)
    React.useEffect(()=>{
        /* 请求数据 , 事件监听 , 操纵dom  , 增加定时器 , 延时器 */
        console.log('组件挂载完成:componentDidMount')
        return function componentWillUnmount(){
            /* 解除事件监听器 ,清除 */
            console.log('组件销毁:componentWillUnmount')
        }
    },[])/* 切记 dep = [] */
    React.useEffect(()=>{
        console.log('props变化:componentWillReceiveProps')
    },[ props ])
    React.useEffect(()=>{ /*  */
        console.log(' 组件更新完成:componentDidUpdate ')
    })
    return <div>
        <div> props : { props.number } </div>
        <div> states : { num } </div>
        <button onClick={ ()=> setNum(state=>state + 1) }   >改变state</button>
    </div>
}
React.useEffect(()=>{
    console.log('组件更新完成:componentDidUpdate ')     
}) /* 没有 dep 依赖项 */

注意此时useEffect没有第二个参数。

没有第二个参数,那么每一次执行函数组件,都会执行该 effect。

总结

image.png