lifeCycle | 青训营

82 阅读7分钟

React两个重要阶段 render阶段:React在render阶段会深度遍历React fiber树,目的是发现不同diff,完成局部更新,并创建虚拟DOM **commit阶段:**会创建修改真实的DOM节点

React 类组件生命周期执行过程

① constructor 执行

在 mount 阶段,首先执行的 constructClassInstance 函数,用来实例化 React 组件,在组件章节已经介绍了这个函数,组件中 constructor 就是在这里执行的。 在实例化组件之后,会调用 mountClassInstance 组件初始化。

② getDerivedStateFromProps 执行

在初始化阶段,getDerivedStateFromProps 是第二个执行的生命周期 ,值得注意的是它是从 ctor 类上直接绑定的静态方法,传入 props ,state 。 返回值将和之前的 state 合并,作为新的 state ,传递给组件实例使用。

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

④ render 函数执行 到此为止 mountClassInstancec 函数完成,但是上面 updateClassComponent 函数, 在执行完 mountClassInstancec 后,执行了 render 渲染函数,形成了 children , 接下来 React 调用 reconcileChildren 方法深度调和 children 。

⑤componentDidMount执行 在组件初始化 commit 阶段,会调用 componentDidMount 生命周期。

componentDidMount 和 componentDidUpdate执行时间

component是针对初始化 componentDidUpdate是针对组件的在更新

执行顺序 constructor -> getDerivedStateFromProps / componentWillMount -> render -> componentDidMount

image.png

类组件更新阶段 执行顺序 componentWillReceiveProps(props更新)/getDerivedStateFromProp -> shouldComponentUpdate -> componentWillUpdate ->render ->getSnapshitBeforeUpdate -> componentDidUpdate

image.png

组件初始化,组件更新,组件销毁

React各个lifecycle功能

1 constructor

React 在不同时期抛出不同的生命周期钩子,也就意味这这些生命周期钩子的使命。上面讲过 constructor 在类组件创建实例时调用,而且初始化的时候执行一次,所以可以在 constructor 做一些初始化的工作。

  • 初始化 state ,比如可以用来截取路由中的参数,赋值给 state 。
  • 对类组件的事件做一些处理,比如绑定 this , 节流,防抖等。
  • 对类组件进行一些必要生命周期的劫持,渲染劫持,这个功能更适合反向继承的HOC。

2 getDerivedStateFromProps(从props中得到派生的state

getDerivedStateFromProps(nextProps,prevState)
//nextProps 父组件新传递的 props ;
//prevState 传入 getDerivedStateFromProps 待合并的 state 。

它的作用在于初始化和更新阶段,接受父组件的props的数据,对props进行操作,返回值作为新的state合并到state中,供给视图渲染层消费 无论是props改变,更新state,还是forceUpdate都会执行getDerivedStateFromProps

  • 代替 componentWillMountcomponentWillReceiveProps
  • 组件初始化或者更新时,将** props 映射到 state**。
  • **返回值与 state 合并完,可以作为 shouldComponentUpdate 第二个参数 **newState ,可以判断是否渲染组件。
  • (请不要把 **getDerivedStateFromProps shouldComponentUpdate **强行关联到一起,两者没有必然联系)

3 componentWillReceiveProps 和 UNSAFE_componentWillReceiveProps

UNSAFE_componentWillReceiveProps 函数的执行是在更新组件阶段,该生命周期执行驱动是因为父组件更新带来的 props 修改

只要父组件触发 render 函数,调用 React.createElement 方法,那么** props 就会被重新创建**,生命周期 componentWillReceiveProps 就会执行了。这就解释了即使 props 没变,该生命周期也会执行。

(pureComponent组件不能阻止props不变的时候,执行componentWillReceiveProps 纯组件只是在 componentWillReceiveProps 执行之后浅比较 props 是否发生变化。所以 PureComponent 下不会阻止该生命周期的执行。

替代方案

React.useEffect(()=>{
    console.log('props变化:componentWillReceiveProps')
},[ props ])

componentWillUpdate 和 UNSAFE_componentWillUpdate

DOM 还没有更新。在这里可以做一些获取 DOM 的操作。就比如说在一次更新中,保存 DOM 之前的信息(记录上一次位置)。 但是 React 已经出了新的生命周期 getSnapshotBeforeUpdate 来代替 UNSAFE_componentWillUpdate。

render

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

7 getSnapshotBeforeUpdate( 获取更新前的快照)

getSnapshotBeforeUpdate(prevProps,preState){}
  • prevProps更新前的props ;
  • preState更新前的state

我们可以理解成更新前的DOM状态,此时 DOM 还没有更新,但是在接下来的 Mutation 阶段会被替换成真实 DOM ,getSnapshotBeforeUpdate 将返回一个值作为一个snapShot(快照),传递给 componentDidUpdate作为第三个参数。(二者相辅相成)

作用:

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

8 componentDidUpdate

有三个参数

  • prevProps 更新之前的 props ;
  • prevState 更新之前的 state ;
  • snapshot 为 getSnapshotBeforeUpdate 返回的快照,可以是更新前的 DOM 信息。

作用:

  • componentDidUpdate 生命周期执行,此时 DOM 已经更新,可以直接获取 DOM 最新状态。这个函数里面如果想要使用 setState ,一定要加以限制,否则会引起无限循环。
  • 接受 getSnapshotBeforeUpdate 保存的快照信息。

9 componentDidMount

与componentDidUpdate相似,前者是初始化 后者是组件更新 此时可以做一些DOM操作

作用:

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

替代方案

React.useEffect(()=>{
    /* 请求数据 , 事件监听 , 操纵dom */
},[])  /* 切记 dep = [] */

这里的dep=[]代表没有依赖,只是初始化的时候执行

10 shouldComponentUpdate

shouldComponentUpdate 三个参数,第一个参数新的 props ,第二个参数新的 state ,第三个参数新的 context 。

作用:shouldComponentUpdate 返回值决定是否重新渲染的类组件。

第二个参数 newState ,如果有 getDerivedStateFromProps 生命周期 ,它的返回值将合并到 newState ,供 shouldComponentUpdate 使用。

11 componentWillUnmount

componentWillUnmount 是组件销毁阶段唯一执行的生命周期,主要做一些收尾工作,比如清除一些可能造成内存泄漏的定时器,延时器,或者是一些事件监听器。

作用:

  • 清除延时器,定时器。
  • 一些基于 DOM 的操作,比如事件监听器。
 React.useEffect(()=>{
        /* 请求数据 , 事件监听 , 操纵dom , 增加定时器,延时器 */
        return function componentWillUnmount(){
            /* 解除事件监听器 ,清除定时器,延时器 */
        }
},[])/* 切记 dep = [] */

useEffect

useEffect 第一个参数 callback, 返回的 destory , destory 作为下一次callback执行之前调用用于清除上一次 callback 产生的副作用

第二个参数作为依赖项,是一个数组,可以有多个依赖项,依赖项改变,执行上一次callback 返回的 destory ,和执行新的 effect 第一个参数 callback 。 对于 useEffect 执行,** React 处理逻辑是采用异步调用 **,对于每一个 effect 的 callback, React 会向 setTimeout回调函数一样,放入任务队列等到主线程任务完成,DOM 更新,js 执行完成,视图绘制完毕,才执行。所以 effect 回调函数不会阻塞浏览器绘制视图。

useLayoutEffect: useLayoutEffect 和 useEffect 不同的地方是采用了同步执行 二者的差异在于: 前者实在视图绘制之前执行 后者是在视图绘制之后 首先 useLayoutEffect 是在 DOM 更新之后,浏览器绘制之前, 样可以方便修改 DOM,获取 DOM 信息 这样好处是浏览器只绘制一次 useLayoutEffect callback 中代码执行会阻塞浏览器绘制。

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

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

useInsertionEffect(React 18

**useInsertionEffect 的执行时机要比 useLayoutEffect 提前,**useLayoutEffect 执行的时候 DOM 已经更新了,但是在 useInsertionEffect 的执行的时候,DOM 还没有更新。

本质上 useInsertionEffect 主要是解决 CSS-in-JS 在渲染中注入样式的性能问题


使用useEffect来模拟生命周期

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 ')
    })//useEffect在没有依赖项的时候会在每次渲染的时候执行回调
    return <div>
        <div> props : { props.number } </div>
        <div> states : { num } </div>
        <button onClick={ ()=> setNum(state=>state + 1) }   >改变state</button>
    </div>
}

export default ()=>{
    const [ number , setNumber ] = React.useState(0)
    const [ isRender , setRender ] = React.useState(true)
    return <div>
        { isRender &&  <FunctionLifecycle number={number}  /> }
        <button onClick={ ()=> setNumber(state => state + 1 ) } > 改变props  </button> <br/>
        <button onClick={()=> setRender(false) } >卸载组件</button>
    </div>
}