React18原理: 生命周期中特别注意事项
概述
- 生命周期就是一个组件从诞生到销毁的全过程(包含错误捕获,这里暂且不聊这个)
- react 在组件的生命周期中注册了一系列的钩子函数
- 支持开发者在其中嵌入代码,并在适当的时机运行
- 生命周期本质上就是组件中的钩子函数,主要有三个主要的钩子
- 挂载
- 更新
- 卸载
首次挂载
-
1 )初始化 constructor
- 同一个类组件对象只会运行一次
- 所以经常来做一些初始化的操作
- 同一个组件对象被多次创建,它们的 constructor 互不干扰
- 注意
- 在 constructor 中尽量避免(禁止) 使用 setState
- setState会造成页面的重新渲染
- 但是在 初始化 阶段,页面都还没有将真实dom挂载到页面上,是没有任何重新渲染的意义的
- 除异步情况,比如 setInterval 中使用 setState 是没问题的,
- 因为在执行的时候页面早已渲染完成,但也最好不要使用,容易引起奇怪的问题,参考
constructor(props) { super(props); this.state = { num: 1 } // 不可以,直接warning this.setState({ num: this.state.num + 1 }); // 可以使用,但不建议 setInterval(() => { this.setState({ num: this.state.num + 1 }); }, 1000); }
-
2 )获取最新的属性和状态 static getDerivedStateFromProps
- 该方法是一个静态属性,静态属性就是为了不让使用 this 和 setState
- 在16版本之前不存在,在新版生命周期中,主要用于取代 componentWillMount 和 comonentWillReceiveProps
- 因为这两个老生命周期方法在协议开发者不规范的使用下极易产生一些反模式的 bug
- 因为是静态方法,所以,你在其中根本拿不到 this, 更不可能调用 setState
- 该方法在挂载阶段和更新阶段都会运行,它有两个参数 props 和 state 当前的属性值和状态
- 它的返回值会合并掉当前的状态 (state), 如果返回了非 Object 的值
- 那么它啥都不会做,如果返回的是 Object, 那它将会跟当前的状态合并
- 可以理解为 Object.assign, 通常情况下,几乎不怎么使用该方法
/** * 静态方法,首次挂载和更新渲染都会运行该方法 * props 当前属性 * state 当前状态 */ static getDerivedStateFromProps(props, state) { // return 1; // 没用 return { num: 999, // 合并到当前 state 对象 } }
-
3 )创建 vDOM render
- 最重要的生命周期,没有之一,用来生成虚拟节点vDom树
- 该方法只要遇到需要重新渲染都会运行
- 同样,在 render 中也严禁使用 setState, 因为会导致无限递归重新渲染导致 爆栈
render() { // 严禁使用 this.setState({ num: 1 }) return ( <> (this.state.num) </> ) }
-
4 )挂载到页面渲染成真实Dom componentDidMount
- 该方法只会运行一次,在 首次渲染 时页面将 真实 dom 挂载完毕之后运行
- 通常在这里做一些异步操作,比如开启定时器,发起网络请求,获取真实DOM等
- 在该方法中,可以大胆使用 setState, 因为页面已经渲染完成
- 执行完该钩子函数后,组件正式进入到 活跃 状态
componentDidMount() { // 初始化或异步代码 this.setState({}) setInterval(() => {}); // 简单模拟 document.querySelectorAll('div'); }
更新阶段
-
更新阶段会更新 state 或 更新 props
-
1 )获取最新的属性和状态 static getDerivedStateFromProps
-
2 )是否重新渲染 shouldComponentUpdate
- 在执行完 static getDerivedStateFromProps 后,会执行该钩子函数
- 该方法通常用来做 性能优化,它的返回值 (boolean) 决定了是否要进行 渲染 更新
- 该方法有两个参数 nextProps 和 nextState 表示此次更新(下一次)的属性和状态
- 通常,我们会将当前值与此次要更新的值做比较来决定是否要进行重新渲染
- 在react 中,官方提供了一个基础版的优化组件 PureComponent 就是一个 HOC 高阶组件
- 内部实现就是帮我们用 shouldComponentUpdate 做了浅比较
- 注意,继承了 PureComponent 后, 不需要再使用 SCU 进行优化
/** * 决定是否要重新进行渲染 * nextProps 此次更新的属性 * nextState 此次更新的状态 * returns boolean */ shouldComponentUpdate(nextProps, nextState) { // 伪代码,如果当前的值和下一次的值相等,那么就没有更新渲染的必要了 if (this.props === nextProps && this.state === nextState) { return false; } return true; }
-
3 )更新vDOM render
-
4 )获取更新之前的状态 getSnapshotBeforeUpdate
- 如果 shouldComponentUpdate 返回是 true,
- 那么就会运行 render 重新生成虚拟 DOM 树来进行对比更新
- 该方法运行在 render 之后,表示 真实 DOM 已经构建完成
- 但还没有渲染到页面中,可以理解为更新前的 快照,通常用来做一些附加的DOM操作
- 比如,突然想针对某个 class 的真实元素做一些事情,那么就可以在此方法中获取元素
- 并修改,该函数有两个参数 prevProps 和 prevState 表示此次更新前的属性和状态
- 该函数的返回值 snapshot,会作为 componentDidUpdate 的第三个参数
/** * 获取更新前的快照,通常用来做一些附加的DOM操作 * prevProps 更新前的属性 * prevState 更新前的状态 */ getSnapshotBeforeUpdate(prevProps, prevState) { // 获取真实DOM在渲染到页面前作一些附加操作... document.querySelectorAll('div').forEach(it => it.innerHTML = '123') return 'componentDidUpdate的第三个参数' }
-
5 )更新后挂载成真实DOM componentDidUpdate
- 该方法是 更新阶段 最后运行的 钩子函数,跟
getSnapshotBeforeUpdate
不同的是 - 它运行时间点是在 真实DOM 挂载到页面后,通常也会使用该方法来操作一些真实的DOM
- 它有三个参数分别为: prevProps, prevState, snapshot, 跟 Snapshot 钩子函数一样
- 表示更新前的属性,状态,Snapshot 钩子函数的返回值
/** * prevProps 更新前的属性 * prevState 更新前的状态 * snapshot getSnapshotBeforeUpdate 的返回值 */ componentDidUpdate(prevProps, prevState, snapshot) { document.querySelectorAll('div').forEach(it => it.innerHTML = snapshot) }
- 该方法是 更新阶段 最后运行的 钩子函数,跟
卸载阶段
- 组件被卸载 componentWillUnMount
- 该 钩子函数 属于卸载阶段中唯一的方法
- 如果组件在渲染的过程中被卸载了,React会报出 Warning: Can’t perform a React state update on an unmounted component 的警告
- 所以,通常爱组件被卸载时,做清除副作用的操作
componentWillUnmount() { // 组件倍卸载前清理副作用 clearInterval(timer1); clearTimeout(timer2); this.setState = () => {}; }