生命周期总览
projects.wojtekmaj.pl/react-lifec…
黄色框的生命周期是在React17.0已经移除的生命周期函数
生命周期说明
/* eslint-disable no-script-url */
/* eslint-disable jsx-a11y/anchor-is-valid */
import React, { Component } from "react";
class LifeCycle extends React.Component {
constructor(props) {
super(props);
this.state = {
num: Math.random() * 100,
};
this.childRef = React.createRef();
console.log('parent constructor')
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log("parent getDerivedStateFromProps");
if (nextProps.isUpdate) {
return { str: "getDerivedStateFromProps update state" };
}
return null;
}
// componentWillReceiveProps(nextProps, prevState) {
// debugger
// console.log("componentWillReceiveProps()");
// }
componentDidMount() {
console.log("parent componentDidMount");
// this.setState({
// str: "str",
// });
}
shouldComponentUpdate(nextProps, nextState) {
console.log("parent shouldComponentUpdate");
return true; // 记得要返回true
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("parent getSnapshotBeforeUpdate");
return {
name: "componentWillUpdate",
};
}
// componentWillUpdate(prevProps, prevState) {
// console.log("componentWillUpdate");
// return {
// name: 'componentWillUpdate'
// }
// }
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("parent componentDidUpdate");
}
componentWillUnmount() {
console.log("parent componentWillUnmount");
}
propsChange() {
console.info("更新父组件state");
this.setState({
num: Math.random() * 100,
});
}
setLifeCycleState() {
console.info("更新子组件state");
this.childRef.current.setTheState();
}
forceLifeCycleUpdate() {
console.info("强制更新子组件");
this.childRef.current.forceItUpdate();
}
parentForceUpdate() {
console.info("强制更新父组件");
this.forceUpdate();
}
render() {
console.log("parent render")
return (
<div>
<button
className="weui_btn weui_btn_primary"
onClick={this.propsChange.bind(this)}
>
更新父组件state
</button>
<button
className="weui_btn weui_btn_primary"
onClick={this.setLifeCycleState.bind(this)}
>
更新子组件state
</button>
<button
className="weui_btn weui_btn_primary"
onClick={this.forceLifeCycleUpdate.bind(this)}
>
forceUpdate 子组件
</button>
<button
className="weui_btn weui_btn_primary"
onClick={this.parentForceUpdate.bind(this)}
>
forceUpdate 父组件
</button>
<Message ref={this.childRef} num={this.state.num}></Message>
</div>
);
}
}
class Message extends Component {
constructor(props) {
super(props);
console.log("child constructor");
this.state = { str: "hello", name: "rodchen" };
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log("child getDerivedStateFromProps");
if (nextProps.isUpdate) {
return { str: "getDerivedStateFromProps update state" };
}
return null;
}
// componentWillReceiveProps(nextProps, prevState) {
// debugger
// console.log("componentWillReceiveProps()");
// }
componentDidMount() {
console.log("child componentDidMount");
// this.setState({
// str: "str",
// });
}
shouldComponentUpdate(nextProps, nextState) {
console.log("child shouldComponentUpdate");
return true; // 记得要返回true
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("child getSnapshotBeforeUpdate");
return {
name: "componentWillUpdate",
};
}
// componentWillUpdate(prevProps, prevState) {
// console.log("componentWillUpdate");
// return {
// name: 'componentWillUpdate'
// }
// }
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("child componentDidUpdate");
}
componentWillUnmount() {
console.log("child componentWillUnmount");
}
setTheState() {
let s = "hello";
if (this.state.str === s) {
s = "HELLO";
}
this.setState({
str: s,
});
}
forceItUpdate() {
this.forceUpdate();
}
render() {
console.log("child render");
return (
<div>
<span>
Props:<h2>{this.props.num}</h2>
</span>
<span>
State:<h2>{this.state.str}</h2>
</span>
</div>
);
}
}
export default LifeCycle;
monting阶段
Contructor
import React from "react";
class LifeCycle extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'rodchen'
}
}
render() {
return (
<div>
<h2>Life Cycle</h2>
<div>
</div>
</div>
);
}
}
export default LifeCycle;
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。
通常,在 React 中,构造函数仅用于以下两种情况:
- 通过给 this.state 赋值对象来初始化内部 state。
- 为事件处理函数绑定实例
在 constructor() 函数中不要调用 setState() 方法。如果你的组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始 state:
避免将 props 的值复制给 state
constructor(props) {
super(props);
// 不要这样做
this.state = { color: props.color };
}
当props更新不会更新到state。只有在你刻意忽略 prop 更新的情况下使用。此时,应将 prop 重命名为 initialColor 或 defaultColor。必要时,你可以修改它的 key,以强制“重置”其内部 state。
getDerivedStateFromProps
getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
static getDerivedStateFromProps(nextProps, prevState) {
console.log("getDerivedStateFromProps");
return {str: "getDerivedStateFromProps update state"};
}
此方法无权访问组件实例。如果你需要,可以通过提取组件 props 的纯函数及 class 之外的状态,在getDerivedStateFromProps()和其他 class 方法之间重用代码。这里使用this是undefined。
请注意,不管原因是什么,都会在每次渲染前触发此方法。这与 UNSAFE_componentWillReceiveProps 形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用 setState 时。
getDerivedStateFromProps
UNSAFE_componentWillReceiveProps
componentWillReceiveProps 可以访问组件实例,this是当前的组件
因为不管什么原因,每次渲染都会调用这个方法,所以默认返回null。如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的,所以尽量将其写到函数的末尾
static getDerivedStateFromProps(nextProps, prevState) {
console.log("getDerivedStateFromProps");
if (nextProps.isUpdate) {
return {str: "getDerivedStateFromProps update state"};
}
return null;
}
render() {
// console.log("render");
return (
<div>
<span>Props:<h2>{this.props.num}</h2></span>
<br/>
<span>State:<h2>{this.state.str}</h2></span>
</div>
);
}
render
render() 方法是 class 组件中唯一必须实现的方法。
当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:
- React 元素。通常通过 JSX 创建。例如,会被 React 渲染为 DOM 节点, 会被 React 渲染为自定义组件,无论是还是 均为 React 元素。
- 数组或 fragments。 使得 render 方法可以返回多个元素。欲了解更多详细信息,请参阅 fragments 文档。
- Portals。可以渲染子节点到不同的 DOM 子树中。欲了解更多详细信息,请参阅有关 portals 的文档
- 字符串或数值类型。它们在 DOM 中会被渲染为文本节点
- 布尔类型或 null。什么都不渲染。(主要用于支持返回 test && 的模式,其中 test 为布尔类型。)
render() 函数应该为纯函数
这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。
如需与浏览器进行交互,请在 componentDidMount() 或其他生命周期方法中执行你的操作。保持 render() 为纯函数,可以使组件更容易思考。
如果 shouldComponentUpdate() 返回 false,则不会调用 render()。
shouldComponentUpdate() {
console.log("shouldComponentUpdate");
return true; // 记得要返回true
}
shouldComponentUpdate() {
console.log("shouldComponentUpdate");
return false; // 记得要返回true
}
componentDidMount
componentDidMount() {
console.log("componentDidMount");
}
componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅
你可以在 componentDidMount() 里直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。
componentDidMount() {
console.log("componentDidMount");
this.setState({
str: 'str'
})
}
为什么componentDidMount的执行在父节点之前?
- 常识理解:父子节点挂在dom节点,如果子节点没有挂载成功,那父组件是不能够表明自己已经挂载成功。
- 流程说明:Diff算法,render阶段的时候,每个节点的completeWork方法中,会进行当前effectTag节点的标识和建立链表结构effectList。completeWork的节点也是从子节点向上依次执行的,所以在render阶段成功之后,根节点上会具有一条所有标识effectTag需要更新的链表。firstEffectTag指向第一个需要更新的叶子节点。这里做这个链表是为了优化,不能render阶段进行diff算法,到了commit还需要再次进行diff算法。
Updating
更新例子
手动更新父组件state
手动更新子组件state
强制更新父组件state
强制更新子组件state
shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
console.log("shouldComponentUpdate");
return true; // 记得要返回true
}
根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。
当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。
此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。你应该考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()。PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。
如果你一定要手动编写此函数,可以将 this.props 与 nextProps 以及 this.state 与nextState 进行比较,并返回 false 以告知 React 可以跳过更新。请注意,返回 false 并不会阻止子组件在 state 更改时重新渲染。
我们不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。
首次渲染或使用 forceUpdate() 时不会调用该方法
初始加载:
forceUpdate:
返回false
如果 shouldComponentUpdate() 返回 false,则不会调用 UNSAFE_componentWillUpdate(),render() 和 componentDidUpdate()
shouldComponentUpdate(nextProps, nextState) {
console.log("shouldComponentUpdate");
return false; // 记得要返回true
}
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。
此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。
应返回 snapshot 的值(或 null)。
这个新更新代替componentWillUpdate。 常见的 componentWillUpdate 的用例是在组件更新前,读取当前某个 DOM 元素的状态,并在 componentDidUpdate 中进行相应的处理。
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("getSnapshotBeforeUpdate");
return {
name: 'componentWillUpdate'
}
}
// componentWillUpdate(prevProps, prevState) {
// console.log("componentWillUpdate");
// return {
// name: 'componentWillUpdate'
// }
// }
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("componentDidUpdate");
debugger
console.log(snapshot)
}
componentWillUpdate & getSnapshotBeforeUpdate的区别
- 在 React 开启异步渲染模式后,在 render 阶段读取到的 DOM 元素状态并不总是和 commit 阶段相同,这就导致在componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了。
- getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。
- 此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。
当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。你也可以在 componentDidUpdate() 中直接调用 setState(),但请注意它必须被包裹在一个条件语句里,正如上述的例子那样进行处理,否则会导致死循环。
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
getSnapshotBeforeUpdate & componentDidUpdate的顺序
看上面手动更新父组件state,产生的更新。生命周期执行的顺序如下。这里getSnapshotBeforeUpdate和componentDidUpdate的执行顺序是分开的,同样都是从子组件开始,然后到父组件。
这里要从commit的三个阶段来说:commit分为三个阶段
- before mutation 阶段:操作DOM之前【getSnapshotBeforeUpdate】
- mutation 阶段:操作DOM【componentWillUnmount】
- layout 阶段:操作DOM之后【componentDidMount,componentDidUpdate】
和上面为什么componentDidMount的执行在父节点之前?说的一样,commit阶段主要是在遍历在render阶段形成effectList。因为effectList的顺序是从叶子节点开始的。所以这里的顺序是从子节点到父节点。那么为什么分为两个批次。因为这两个方法是在commit的不同阶段执行的。
Unmounting
componentWillUnmount
componentWillUnmount()
componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
卸载组件
这里的原因是当父节点在进行diff算法的时候,标识当前节点需要删除,则会结束当前节点的继续遍历。而在commt的mutation阶段,进行delete case的时候,会对父节点进行遍历所有子节点,移除进行delete操作。
性能优化
React.PureComponent
React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。
如果赋予 React 组件相同的 props 和 state,render() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能。
React.memo
React.memo 为高阶组件。
如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
参与评论讨论有奖
- 生命周期的组件的问题讨论
- 关于react hooks的问题探讨
- 奖品:掘金徽章【仅限两名】
- 时间:截止9/10号
- 活动链接:juejin.cn/post/700064…