类组件声明周期
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。