lifeCycle
lifeCycle
生命周期钩子函数相信应该都不陌生,它能够帮助我们在一些重要的React阶段做一些处理,接下来将分析React生命周期的流程和每个生命周期所做的事情。
类组件生命周期
React有两个重要的阶段,分别为render阶段和commit阶段,在state中也有提到这两个阶段,React在调和(render)阶段会深度遍历React fiber 树,目的就是发现不同,不同的地方就是接下来需要更新的地方,对于变化的组件,就会执行 render 函数。在一次调和过程完毕之后,就到了commit 阶段,commit 阶段会创建修改真实的 DOM 节点,这就是render阶段和commit阶段。可以将一个组件的初始化或者更新分为三个阶段,分别为组件初始化,组件更新 , 组件销毁。
组件初始化
-
constructor
在mount阶段会执行component提到的一个函数constructClassInstance,通过这个函数来实例化React组件,实例完成之后需要对其进行初始化,这个时候就会调用mountClassInstance函数对组件进行初始化。
function mountClassInstance(workInProgress,ctor,newProps,renderExpirationTime){ const instance = workInProgress.stateNode; const getDerivedStateFromProps = ctor.getDerivedStateFromProps; if (typeof getDerivedStateFromProps === 'function') { /* ctor 就是我们写的类组件,获取类组件的静态方法 */ const partialState = getDerivedStateFromProps(nextProps, prevState); /* 这个时候执行 getDerivedStateFromProps 生命周期 ,得到将合并的state */ const memoizedState = partialState === null || partialState === undefined ? prevState : Object.assign({}, prevState, partialState); // 合并state workInProgress.memoizedState = memoizedState; instance.state = workInProgress.memoizedState; /* 将state 赋值给我们实例上,instance.state 就是我们在组件中 this.state获取的state*/ } if(typeof ctor.getDerivedStateFromProps !== 'function' && typeof instance.getSnapshotBeforeUpdate !== 'function' && typeof instance.componentWillMount === 'function' ){ instance.componentWillMount(); /* 当 getDerivedStateFromProps 和 getSnapshotBeforeUpdate 不存在的时候 ,执行 componentWillMount*/ } }
- workInProgress为当前调和的fiber对象,fiber能通过.stateNode来访问当前组件的实例,组件实例能通过_reactInternals获取到当前的fiber对象
- ctor就是我们所传入的类组件
- newProps是传入需要混入的props
- renderExpirationTime是下次渲染的过期时间
constructor作用:
- 初始化 state ,比如可以用来截取路由中的参数,赋值给 state 。
- 对类组件的事件做一些处理,比如绑定 this , 节流,防抖等。
- 对类组件进行一些必要生命周期的劫持,渲染劫持,这个功能更适合反向继承的HOC ,在 HOC 环节,会详细讲解反向继承这种模式。
-
getDerivedStateFromProps
getDerivedStateFromProps是第二个执行的生命周期函数,需要注意的是这个生命周期是绑定在ctor类上的静态方法,传入state,props后返值与之前的state合并作为新的state传给组件实例使用。
getDerivedStateFromProps(nextProps,prevState)
两个参数:
-
nextProps 父组件新传递的 props ;
-
prevState 传入 getDerivedStateFromProps 待合并的 state 。
值得注意的是,由于是静态方法访问不到this,还有只要是组件发生更新就会执行这个生命周期函数,不管是props改变还是setState、或是forceUpdate。
getDerivedStateFromProps 作用:
-
代替 componentWillMount 和 componentWillReceiveProps
-
组件初始化或者更新时,将 props 映射到 state。
-
返回值与 state 合并完,可以作为 shouldComponentUpdate 第二个参数 newState ,可以判断是否渲染组件。(请不要把 getDerivedStateFromProps 和 shouldComponentUpdate 强行关联到一起,两者没有必然联系)
-
-
componentWillMount
由上述代码可知,若存在getDerivedStateFromProps和getSnapshotBeforeUpdate就不会执行componentWillMount函数,值得注意的是在React16.3后已经不推荐使用这个生命周期函数。了解更多去官网查看Component – React
-
render
到此为止 mountClassInstancec 函数完成,接下来就要继续updateClassComponent函数
function updateClassComponent(){ let shouldUpdate const instance = workInProgress.stateNode // stateNode 是 fiber 指向 类组件实例的指针。 if (instance === null) { // instance 为组件实例,如果组件实例不存在,证明该类组件没有被挂载过,那么会走初始化流程 constructClassInstance(workInProgress, Component, nextProps); // 组件实例将在这个方法中被new。 mountClassInstance( workInProgress,Component, nextProps,renderExpirationTime ); //初始化挂载组件流程 shouldUpdate = true; // shouldUpdate 标识用来证明 组件是否需要更新。 }else{ shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderExpirationTime) // 更新组件流程 } if(shouldUpdate){ nextChildren = instance.render(); /* 执行render函数 ,得到子节点 */ reconcileChildren(current,workInProgress,nextChildren,renderExpirationTime) /* 继续调和子节点 */ } }
执行完mountClassInstance后进行调用render函数获得children,在调用reconcileChildren函数对children进行调和。
- instance:组件实例
- current树:在初始化更新中,current = null ,在第一次 fiber 调和之后,会将 workInProgress 树赋值给 current 树。React 来用workInProgress 和 current 来确保一次更新中,快速构建,并且状态不丢失。
- Component:项目中的class组件
还记得 jsx 主要提到了 render 之后会成什么样子。所谓 render 函数,就是 jsx 的各个元素被 React.createElement 创建成 React element 对象的形式。一次 render 的过程,就是创建 React.element 元素的过程。
-
那么可以在render里面做一些,createElement创建元素 , cloneElement 克隆元素 ,React.children 遍历 children 的操作。
-
componentDidMount
到这mount阶段已经结束了,render阶段也已经结束了,到这个时候componentDidMount阶段才到来,它组件初始化commit阶段,会调用这个生命周期函数。
function commitLifeCycles(finishedRoot,current,finishedWork){ switch (finishedWork.tag){ /* fiber tag 在前面讲了不同fiber类型 */ case ClassComponent: { /* 如果是 类组件 类型 */ const instance = finishedWork.stateNode /* 类实例 */ if(current === null){ /* 类组件第一次调和渲染 */ instance.componentDidMount() }else{ /* 类组件更新 */ instance.componentDidUpdate(prevProps,prevState, instance.__reactInternalSnapshotBeforeUpdate); } } } }
从这里可以明显的看到,componentDidMount和ComponentDidUpdata这两个生命周期函数执行的时机是完全相同的,有区别的就是一个针对初始化一个针对的是组件的更新。初始化阶段到这里就已经结束了。
- 可以做一些关于 DOM 操作,比如基于 DOM 的事件监听器。
- 对于初始化向服务器请求数据,渲染视图,这个生命周期也是可以的。
总结顺序:constructor -> getDerivedStateFromProps / componentWillMount -> render -> componentDidMount
更新阶段
在上述的updateClassComponent函数中若current不为null时,说明组件被挂载过,则直接按照组件更新的逻辑处理。
处理函数如下:
function updateClassInstance(current,workInProgress,ctor,newProps,renderExpirationTime){
const instance = workInProgress.stateNode; // 类组件实例
const hasNewLifecycles = typeof ctor.getDerivedStateFromProps === 'function' // 判断是否具有 getDerivedStateFromProps 生命周期
if(!hasNewLifecycles && typeof instance.componentWillReceiveProps === 'function' ){
if (oldProps !== newProps || oldContext !== nextContext) { // 浅比较 props 不相等
instance.componentWillReceiveProps(newProps, nextContext); // 执行生命周期 componentWillReceiveProps
}
}
let newState = (instance.state = oldState);
if (typeof getDerivedStateFromProps === 'function') {
ctor.getDerivedStateFromProps(nextProps,prevState) /* 执行生命周期getDerivedStateFromProps ,逻辑和mounted类似 ,合并state */
newState = workInProgress.memoizedState;
}
let shouldUpdate = true
if(typeof instance.shouldComponentUpdate === 'function' ){ /* 执行生命周期 shouldComponentUpdate 返回值决定是否执行render ,调和子节点 */
shouldUpdate = instance.shouldComponentUpdate(newProps,newState,nextContext,);
}
if(shouldUpdate){
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(); /* 执行生命周期 componentWillUpdate */
}
}
return shouldUpdate
}
-
componentWillReceiveProps
首先判断 getDerivedStateFromProps 生命周期是否存在,如果不存在就执行componentWillReceiveProps生命周期。传入该生命周期两个参数,分别是 newProps 和 nextContext 。前往官网查看:Component – React
-
getDerivedStateFromProps
接下来执行生命周期getDerivedStateFromProps, 返回的值用于合并state,生成新的state。
-
shouldComponentUpdate
接下来执行生命周期shouldComponentUpdate,传入新的 props ,新的 state ,和新的 context ,返回值决定是否继续执行 render 函数,调和子节点。这里应该注意一个问题,getDerivedStateFromProps 的返回值可以作为新的 state ,传递给 shouldComponentUpdate 。
-
componentWillUpdate
接下来执行生命周期 componentWillUpdate。前往官网查看:Component – React
-
render
接下来会执行 render 函数,得到最新的 React element 元素。然后继续调和子节点。
-
getSnapshotBeforeUpdate
function commitBeforeMutationLifeCycles(current,finishedWork){ switch (finishedWork.tag) { case ClassComponent:{ const snapshot = instance.getSnapshotBeforeUpdate(prevProps,prevState) /* 执行生命周期 getSnapshotBeforeUpdate */ instance.__reactInternalSnapshotBeforeUpdate = snapshot; /* 返回值将作为 __reactInternalSnapshotBeforeUpdate 传递给 componentDidUpdate 生命周期 */ } } }
getSnapshotBeforeUpdate 的执行也是在 commit 阶段,commit 阶段细分为 before Mutation( DOM 修改前),Mutation ( DOM 修改),Layout( DOM 修改后) 三个阶段,getSnapshotBeforeUpdate 发生在before Mutation 阶段,生命周期的返回值,将作为第三个参数 __reactInternalSnapshotBeforeUpdate 传递给 componentDidUpdate 。
getSnapshotBeforeUpdate(prevProps,preState){}
两个参数:
- prevProps更新前的props ;
- preState更新前的state;
在DOM被替换之前保存DOM信息getSnapshotBeforeUpdate 将返回一个值作为一个snapShot(快照),传递给 componentDidUpdate作为第三个参数。
作用:
- getSnapshotBeforeUpdate 这个生命周期意义就是配合componentDidUpdate 一起使用,计算形成一个 snapShot 传递给 componentDidUpdate 。保存一次更新前的信息。
-
componentDidUpdate
接下来执行生命周期 componentDidUpdate ,此时 DOM 已经修改完成。可以操作修改之后的 DOM 。到此为止更新阶段的生命周期执行完毕。
三个参数:
- prevProps 更新之前的 props ;
- prevState 更新之前的 state ;
- snapshot 为 getSnapshotBeforeUpdate 返回的快照,可以是更新前的 DOM 信息。
销毁阶段
销毁阶段就比较简单了,在一次调和更新中,如果发现元素被移除,就会打对应的 Deletion 标签 ,然后在 commit 阶段就会调用 componentWillUnmount 生命周期,接下来统一卸载组件以及 DOM 元素。
shouldComponentUpdate
shouldComponentUpdate(newProps,newState,nextContext){}
shouldComponentUpdate 三个参数,第一个参数新的 props ,第二个参数新的 state ,第三个参数新的 context 。
这个生命周期,一般用于性能优化,shouldComponentUpdate 返回值决定是否重新渲染的类组件。需要重点关注的是第二个参数 newState ,如果有 getDerivedStateFromProps 生命周期 ,它的返回值将合并到 newState ,供 shouldComponentUpdate 使用。
componentWillUnmount
作用
- 清除延时器,定时器。
- 一些基于 DOM 的操作,比如事件监听器。
图片来源《React进阶指南》,写的嘎嘎好。
函数式组件中的替代方法
React hooks提供的api的出现弥补了函数式组件没有生命周期的缺陷,为啥没有生命周期在component中有提到,函数式组件的更新是直接重新执行一个函数。其原理主要是运用了 hooks 里面的 useEffect 和 useLayoutEffect这两个hooks。
useEffect和useLayoutEffect
-
useEffect
useEffect(()=>{ return destory },dep)
第一个参数为callback函数放回一个destory函数,这个函数会在下一次callback函数执行时被调用,清除上次callback函数产生的副作用,第二个参数为依赖项,为一个数组, 当依赖项发生改变时就会执行上一次callback返回的destory函数和下一个callback函数。
useEffect的执行是异步操作,和setTimeout回调函数一样,所以不会堵塞浏览器绘制视图。
-
useLayoutEffect
useLayoutEffect 和 useEffect 不同的地方是采用了同步执行,useLayoutEffect 是在 DOM 更新之后,浏览器绘制之前,这样可以方便修改 DOM,获取 DOM 信息,这样浏览器只会绘制一次,如果修改 DOM 布局放在 useEffect ,那 useEffect 执行是在浏览器绘制视图之后,接下来又改 DOM ,就可能会导致浏览器再次回流和重绘。
useInsertionEffect
useInsertionEffect 是在 React v18 新添加的 hooks ,它的用法和 useEffect 和 useLayoutEffect 一样。useInsertionEffect 的执行时机要比 useLayoutEffect 提前,useLayoutEffect 执行的时候 DOM 已经更新了,但是在 useInsertionEffect 的执行的时候,DOM 还没有更新。
这个钩子函数的本质是用来解决CSS-in-JS的问题的,具体的原因可以去官网查看:useInsertionEffect – React
在类组件中的生命周期函数都可以用上述的三个hooks去替代。具体实现查看《React进阶指南》。
总结:
- 类组件生命周期执行过程,以及细节;
- 讲解了类组件各个生命周期,每个生命周期能做的事情;
- 函数组件生命周期代替方案;
转载自:https://juejin.cn/post/7253251884524814391