React生命周期
React
的生命周期从广义上分为挂载、渲染、卸载三个阶段,在React
的整个生命周期中提供很多钩子函数在生命周期的不同时刻调用。
描述
此处描述的是使用class
类组件提供的生命周期函数,每个组件都包含自己的生命周期方法,通过重写这些方法,可以在运行过程中特定的阶段执行这些方法,常用的生命周期有constructor()
、render()
、componentDidMount()
、componentDidUpdate()
、componentWillUnmount()
。
挂载过程
当组件实例被创建并插入DOM
中时,其生命周期调用顺序如下:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
在这个阶段的componentWillMount()
生命周期即将过时,在新代码中应该避免使用。
更新过程
当组件的props
或state
发生变化时会触发更新,组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
在这个阶段的componentWillUpdate()
、componentWillReceiveProps()
生命周期即将过时,在新代码中应该避免使用。
卸载过程
当组件从DOM
中移除时,组件更新的生命周期调用顺序如下:
componentWillUnmount()
错误处理
当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:
static getDerivedStateFromError()
componentDidCatch()
生命周期
constructor()
在React
组件挂载之前,会调用它的构造函数,如果不初始化state
或不进行方法绑定,则不需要为React
组件实现构造函数。在为React.Component
子类实现构造函数时,应在其他语句之前前调用super(props)
,否则this.props
在构造函数中可能会出现未定义的错误。
通常在React
中构造函数仅用于以下两种情况:
- 通过给
this.state
赋值对象来初始化内部state
。 - 为事件处理函数绑定实例。
constructor(props) {
super(props);
}
static getDerivedStateFromProps()
getDerivedStateFromProps
静态方法会在调用render
方法之前调用,并且在初始挂载及后续更新时都会被调用,它应返回一个对象来更新state
,如果返回null
则不更新任何内容。此方法无权访问组件实例,如果确实需要,可以通过提取组件props
的纯函数及class
之外的状态,在getDerivedStateFromProps()
和其他class
方法之间重用代码。此外,不管原因是什么,都会在每次渲染前触发此方法。
static getDerivedStateFromProps(props,state) {}
render()
render()
方法是class
组件中唯一必须实现的方法,render()
函数应该为纯函数,这意味着在不修改组件state
的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。如需与浏览器进行交互,请在componentDidMount()
或其他生命周期方法中执行操作,保持render()
为纯函数。当render
被调用时,它会检查this.props
和this.state
的变化并返回以下类型之一:
-
React
元素,通常通过JSX
创建,例如<div />
会被React
渲染为DOM
节点,<MyComponent />
会被React
渲染为自定义组件,无论是<div />
还是<MyComponent />
均为React
元素。 - 数组或
fragments
,使得render
方法可以返回多个元素。 -
Portals
,可以渲染子节点到不同的DOM
子树中。 - 字符串或数值类型,它们在
DOM
中会被渲染为文本节点。 - 布尔类型或
null
,什么都不渲染,主要用于支持返回test && <Child />
的模式,其中test
为布尔类型。
render() {}
componentDidMount()
componentDidMount()
会在组件挂载后(即插入DOM
树后)立即调用,依赖于DOM
节点的初始化应该放在这里,如需通过网络请求获取数据,此处是实例化请求的好地方。这个方法是比较适合添加订阅的地方,如果添加了订阅,请不要忘记在componentWillUnmount()
里取消订阅。
你可以在componentDidMount()
里直接调用setState()
,它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前,如此保证了即使在render()
两次调用的情况下,用户也不会看到中间状态,请谨慎使用该模式,因为它会导致性能问题。通常应该在constructor()
中初始化state
,如果你的渲染依赖于DOM
节点的大小或位置,比如实现modals
和tooltips
等情况下,你可以使用此方式处理。
componentDidMount() {}
shouldComponentUpdate()
当props
或state
发生变化时,shouldComponentUpdate()
会在渲染执行之前被调用,返回值默认为true
,首次渲染或使用forceUpdate()
时不会调用该方法。根据shouldComponentUpdate()
的返回值,判断React
组件的输出是否受当前state
或props
更改的影响。默认行为是state
每次发生变化组件都会重新渲染,大部分情况下,你应该遵循默认行为。
此方法仅作为性能优化的方式而存在,不要企图依靠此方法来阻止渲染,因为这可能会产生bug
,你应该考虑使用内置的PureComponent
组件,而不是手动编写shouldComponentUpdate()
,PureComponent
会对props
和state
进行浅层比较,并减少了跳过必要更新的可能性。
如果你一定要手动编写此函数,可以将this.props
与nextProps
以及this.state
与nextState
进行比较,并返回false
以告知React
可以跳过更新。请注意,返回false
并不会阻止子组件在state
更改时重新渲染。不建议在shouldComponentUpdate()
中进行深层比较或使用JSON.stringify()
,这样非常影响效率,且会损害性能。目前如果shouldComponentUpdate()
返回false
,则不会调用UNSAFE_componentWillUpdate()
,render()
和componentDidUpdate()
。后续版本React
可能会将shouldComponentUpdate
视为提示而不是严格的指令,并且当返回false
时仍可能导致组件重新渲染。
shouldComponentUpdate(nextProps,nextState) {}
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate()
在最近一次渲染输出(提交到DOM
节点)之前调用,它使得组件能在发生更改之前从DOM
中捕获一些信息(例如滚动位置),此生命周期的任何返回值将作为参数传递给componentDidUpdate()
,该方法应返回snapshot
的值或null
。
此用法并不常见,但它可能出现在UI
处理中,如需要以特殊方式处理滚动位置的聊天线程等。
getSnapshotBeforeUpdate(prevProps,prevState) {}
componentDidUpdate()
componentDidUpdate()
会在更新后会被立即调用,首次渲染不会执行此方法。当组件更新后,可以在此处对DOM
进行操作,如果你对更新前后的props
进行了比较,也可以选择在此处进行网络请求(例如,当props
未发生变化时,则不会执行网络请求。如果shouldComponentUpdate()
返回值为false
,则不会调用componentDidUpdate()
。
你也可以在componentDidUpdate()
中直接调用setState()
,但请注意它必须被包裹在一个条件语句里,否则会导致死循环,因为他将无限次触发componentDidUpdate()
。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。
如果组件实现了getSnapshotBeforeUpdate()
生命周期(不常用),则它的返回值将作为componentDidUpdate()
的第三个参数snapshot
参数传递,否则此参数将为undefined
。
componentDidUpdate(prevProps,prevState,snapshot) {}
componentWillUnmount()
componentWillUnmount()
会在组件卸载及销毁之前直接调用,在此方法中执行必要的清理操作,例如清除timer
、取消网络请求或清除在componentDidMount()
中创建的订阅等。componentWillUnmount()
中不应调用setState()
,因为该组件将永远不会重新渲染,组件实例卸载后,将永远不会再挂载它。
componentWillUnmount() {}
static getDerivedStateFromError()
此生命周期会在后代组件抛出错误后被调用,它将抛出的错误作为参数,并返回一个值以更新state
。getDerivedStateFromError()
会在渲染阶段调用,因此不允许出现副作用,如遇此类情况,请改用componentDidCatch()
。
static getDerivedStateFromError(error) {}
componentDidCatch()
此生命周期在后代组件抛出错误后被调用,componentDidCatch()
会在提交阶段被调用,因此允许执行副作用,它应该用于记录错误之类的情况它接收两个参数:
componentDidCatch(error,info) {}
示例
React
组件的常用生命周期示例。
<!DOCTYPE html>
<html>
<head>
<Meta charset="UTF-8" />
<title>React生命周期</title>
</head>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
console.log("ComponentDidMount",this);
console.log(this.props);
console.log(this.state);
console.log("");
this.timer = setInterval(() => this.tick(),1000);
}
componentWillUnmount() {
console.log("ComponentWillUnmount",this);
console.log(this.props);
console.log(this.state);
console.log("");
clearInterval(this.timer);
}
tick() {
this.setState({ date: new Date() });
}
render() {
return (
<div>
<h1>{this.props.tips}</h1>
<h2>Now: {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
showClock: true,tips: "Hello World!"
}
}
componentDidUpdate(prevProps,prevState) {
console.log("ComponentDidUpdate",this);
console.log(this.props);
console.log(this.state);
console.log("");
}
updateTips() {
this.setState((state,props) => ({
tips: "React update"
}));
}
changeDisplayClock() {
this.setState((state,props) => ({
showClock: !this.state.showClock
}));
}
render() {
return (
<div>
{this.state.showClock && <Clock tips={this.state.tips} />}
<button onClick={() => this.updateTips()}>更新tips</button>
<button onClick={() => this.changeDisplayClock()}>改变显隐</button>
</div>
);
}
}
var vm = ReactDOM.render(
<App />,document.getElementById("root")
);
</script>
</html>
每日一题
https://github.com/WindrunnerMax/EveryDay
参考
https://www.jianshu.com/p/b331d0e4b398
https://www.cnblogs.com/soyxiaobi/p/9559117.html
https://zh-hans.reactjs.org/docs/react-component.html
https://zh-hans.reactjs.org/docs/state-and-lifecycle.html
https://www.runoob.com/react/react-component-life-cycle.html
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/