前言
react生命周期在各个阶段的执行顺序是有所差异的,接下来我们将详细解析react在各个阶段生命周期的执行顺序,以及各个生命周期在组件中充当的作用(以下生命周期指的是react16之后的版本)
组件挂载时
当组件实例被创建并插入DOM时,其生命周期调用顺序如下:
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
组件更新时
当组件的props或state发生变化时会触发更新。组件更新的生命周期调用顺序如下:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
组件卸载时
当组件从DOM中移除时会调用如下方法
- componentWillUnmount()
错误处理
当渲染过程,生命周期或子组件的构造函数中抛出错误时,会调用如下方法:
- static getDerivedStateFromProps()
- componentDidCatch()
常用生命周期
1.render()
render()方法时class组件中唯一必须要实现的方法,当render被调用时,它会检查this.props和this.state的变化并返回下类型:
- React元素:通常通过jsx创建。比如,<div />会被渲染为DOM节点,<MyComponent />会被React渲染成为自定义组件,武林是<div/>还是<MyComponent/>均为React元素。
- 数组或fragments:使得render方法可以返回多个元素,想了解更加详细,参见fragments文档:Fragments – React
- Portals:可以渲染子节点到不同的DOM子树中,
- 字符串或者数值类型:他们在DOM中会被渲染为文本节点
- 布尔类型或null:什么都不渲染(主要用于支持返回test &&<Child />默认,其中test为布尔类型)
render()函数应该为纯函数,这意味着不在修改组件state情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。
如需与浏览器进行交互,请在componentDidMount()或者其他生命周期方法中执行你的操作,保持render()为纯函数,可以使组件更容易思考
注意:shouldComponentUpdate()返回false,则不会调用render()
2.constructor()构造函数
constructor(props)
如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数
在React组件挂在之前,会调用它的构造函数。在为React.Component子类实现构造函数时,应在其他语句之前调用super(props)。斗则,this.props在构造函数中可能会出现未定义的bug
通常,在React中,构造函数仅用于以下两种情况:
- 通过给this.state赋值对象来初始化内部state
- 为事件处理函数绑定实例
在constructor()函数中不要调用setState()方法,如果你的组件需要使用内部state,请直接在构造函数中为this.state赋值初始化state:
constructor(props) {
super(props);
// 不要在这里调用 this.setState()
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
只能在构造函数中直接为this.state赋值,如需要在其他方法中赋值,应该使用this.setState()替代
要避免在构造函数中引入任何副作用或订阅,如遇场景,请将对应的擦欧总放置在componentDidMount中
注意:避免将props的值复制给state!这是一个常见的错误:
constructor(props) { super(props); // 不要这样做 this.state = { color: props.color }; }
如此做毫无必要(你可以直接使用this.props.color),同事还产生了bug(更新prop中的color时,并不影响state)
3.componentDidMount()
componentDidMount()会在组件挂在(插入DOM树中)立即调用。依赖DOM节点的初始化应该放在这里。如需通过网络请求获取数据,此处实例化请求的好地方。
这个方法是比较适合添加订阅的方法,如果添加了订阅,请不要忘记在componentWillUnmount中进行取消
你可以在componentDidMount()里直接调用setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在render()两次调用的情况下,用户也不会看到中间状态。
4.componentDidUpdate()
componentDidUpdate(prevProps,prevState,snapshot)//prevProps表示之前的prop
componentDidUpdate()会在更新后立即被调用,首次渲染不会执行此方法
当组件更新后,可以在此处对DOM进行操作。如果你对更新前后的prop进行了比较,也可以渲染在此处进行网络请求(例如,当props未发生变化时,则不会执行网络请求)
componentDidUpdate(prevProps){
//典型用法(不要忘记比较props)
if(this.props.userId!==prevProps.userId){
this.fetchDate(this.props.userId)
}
}
如果组件实现了getSnapshotBeforeUpdate()生命周期(不常用),则它的返回值将作为componentDidUpdate()的第三个参数“snapshot”参数传递,否则此参数将为undefined。
注意:如果shouldComponentUpdate()返回值未false,则不会调用componentDidUpdate()
5.componentWillUnmount()
componentWillUnmount()
会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount()
中创建的订阅等。
componentWillUnmount()
中不应调用 setState()
,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
不常用的生命周期方法
1.shouldComponentUpdate()
shouldComponentUpdate(nextProps,nextState)
根据shouldComponentUpdate()的返回值,判断React组件的输出时否手当前state或props更改的影响,默认行为时state每次发生改变组件都会渲染,大部分情况下,你应该遵循默认行为。
当props或者state发生改变时,shouldComponentUpdate()会在渲染执行之前被调用。默认返回值未true,首次渲染或使用forceUpdate()时不会调用该方法。
此方法仅作为性能优化方式而存在,不要企图依靠此方法来阻止渲染,因为这可能会尝试bug。你应该考虑使用内置PrueComponent组件,而不是手动编写shouldComponentUpdate(),PureComponent会对props和state进行浅层比较,并减少跳过必要更新的可能性。
2.static getDerivedStateFromProps()
3.getSnapshotBeforeUpdate()
4.static getDerivedStateFromError()
5.componentDidCatch()
其他API
不同于上述声明周期方法(React主动调用),以下方法你可以在组件中调用的方法
只有两个方法:setState()和forceUpdate()
1.setState()
setState(updater,[callback])
setState()将对组件色state的更改排入队列,并通知React需要使用更新后的state重新渲染此组件及其子组件,这是用于更新用户界面以及响应事件处理器和处理服务器数据的主要方式。
将setState()视为请求而不是立即更新组件的命令,为了更好的感知性能,React会延迟调用,通过一次传递更新多个组件,React并不会保证state的变更会立即生效(及setState()对state状态的更新不是同步的)
setState()并不总是立即更新组件,他会批量推迟更新,这使得在调用setState()后立即读取this.state成为隐患。为了消除隐患,请使用componentDidUpdate或者setState的回调函数
setState(updater,callback),这两种方式都可以保证在应用更新后触发。如需基于值卡的state来设置当前的state,请阅读关于参数updater函数的内容。
this.setState((props,state)=>{
return {counter:state.counter+props.step};
})
updater函数中接收的state和props都保证为最新。updater的返回值会与state进行浅合并
setState()的第二个参数为可选的回调函数,它将在setState完成合并并重新渲染组件后执行。通常,我们建议使用componentDidUpdate()来代替此方法
setState()的第一个参数除了接收函数外,还可以接收对象类型
setState(stateChange[, callback])
setChange会将传入的队形浅层合并到新的state。例如,调整购物车商品数:
this.setState({quantity:2})
这种形式的setState()也是异步的,并且在统一周期会对多个setState进行批处理。例如,如果在同意周期内多次设置商品数量增加,则相当于:
Object.assign(
previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)
后调用的setState()将覆盖统一周期内先调用setState的值,因此商品数量仅增加一次,如果后续状态取决于当前状态,我们建议使用updater函数的形式代替:
this.setState((state)=>{
return {quantity:state.quantity+1}
})
2.forceUpdate()
component.forceUpdate(callback)
默认情况下,当组件的state或props发生改变时,组件将重新渲染。如果render()方法依赖于其他数据,则可以调用forceUpdate()强制让组件重新渲染。
调用forceUpdate()将致使组件调用render()方法,此操作会跳过该组件的shouldComponentUpdate(),但其子组件会触发正常的声明周期方法。
通常你应该避免使用 forceUpdate()
,尽量在 render()
中使用 this.props
和 this.state
。