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
类组件更新阶段 执行顺序 componentWillReceiveProps(props更新)/getDerivedStateFromProp -> shouldComponentUpdate -> componentWillUpdate ->render ->getSnapshitBeforeUpdate -> componentDidUpdate
组件初始化,组件更新,组件销毁
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
- 代替 componentWillMount 和 componentWillReceiveProps
- 组件初始化或者更新时,将** 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>
}