React组件的生命周期

时间:2021-05-06 22:05:03

React组件的生命周期和生命周期方法可以分为以下三个部分:
组件的初始化挂载

  • componentWillMount() : 在初始化渲染执行之前调用;
  • componentDidMount() : 在初始化渲染执行之后调用;

组件的更新

  • componentWillReceiveProps(): 在组件接收到新的 props 的时候调用;
  • shouldComponentUpdate() : 在接收到新的 props 或者 state,将要渲染之前调用;
  • componentWillUpdate() : 在接收到新的 props 或者 state 之前立刻调用,使用该方法做一些更新之前的准备工作;
  • componentDidUpdate() : 在组件的更新已经同步到 DOM 中之后立刻被调用,使用该方法可以在组件更新之后操作 DOM 元素;

组件的卸载移除

  • componentWillUnmount() : 在组件从 DOM 中移除的时候立刻被调用,在该方法中执行任何必要的清理。

组件初始化挂载

组件的初始化挂载完成组件的加载初始化操作,此阶段共包含两个方法 componentWillMount()componentDidMount() 这两个函数分别在组件的挂载前后执行,因此在组件的整个生命周期仅执行一次,示例代码如下:

class Test extends React.Component {
constructor() {
super();
this.state={ value: 0 }
}
componentWillMount() {
console.log("Test componentWillMount");
}
componentDidMount() {
console.log("Test componentDidMount");
}
render() {
console.log("Test component render");
console.log("value is: " + this.state.value);P
return null;
}
}
ReactDOM.render(
<Test />,
document.getElementById("react-root")
);

//控制台输出:
Test componentWillMount
Test component render
value is: 0
Test componentDidMount

componentWillMount()

void componentWillMount()

官方文档对这个方法有如下说明:

Invoked once, both on the client and server, immediately before the initial rendering occurs. If you call setState within this method, render() will see the updated state and will be executed only once despite the state change.

方法在组件初始化渲染 render() 执行之前调用,在组件的生命周期中,仅会被执行一次,这个时候 DOM 还没有渲染,在这里可以允许我们在 DOM 渲染之前更改state,
即在这个方法内调用 setState,render() 在渲染 DOM 的时候,会使用更新后的 state 渲染 DOM ,代码如下:

class Test extends React.Component {
constructor() {
super();
this.state={ value: 0 }
}
componentWillMount() {
console.log("Test componentWillMount");
this.state={ value: 1 }
}
componentDidMount() {
console.log("Test componentDidMount");
}
render() {
console.log("Test component render");
console.log("value is: " + this.state.value);
return null;
}
}
ReactDOM.render(
<Test />,
document.getElementById("react-root")
);

//控制台输出:
Test componentWillMount
Test component render
value is: 1
Test componentDidMount

注意到 will be executed only once despite the state change 那么,如果在 componentWillMount() 中异步更新 state 会怎样呢,代码如下:

class Test extends React.Component {
constructor() {
super();
this.state={ value: 0 }
}
componentWillMount() {
console.log("Test componentWillMount");
setTimeout(function() {
console.log("Async load data")
this.setState={ value: 1 }
}.bind(this), 100)
}
componentDidMount() {
console.log("Test componentDidMount");
}
render() {
console.log("Test component render");
console.log("value is: " + this.state.value);
return null;
}
}
ReactDOM.render(
<Test />,
document.getElementById("react-root")
);

//控制台输出:
Test componentWillMount
Test component render
value is: 0
Test componentDidMount
Async load data

从结果可以看到,异步改变的 state ,并没有影响到原有的 DOM render(), 控制台输出为 value is: 0。而且异步加载完成后对 componentWillMount() 中 state 的改变,控制台并没有再次输出 Test component rendervalue is: 1 ,没有再次触发 render() 过程。

总结:
componentWillMount() 方法中对 state 的改变,如果是同步操作可以生效,异步操作会被忽略,因为异步操作完成后,组件状态不确定。
React 官方不建议在 componentWillMount() 修改 state ,通常建议在 componentDidMount(), 如果需要设置 state 的初始状态,可以在 (es6:)constractor() 或者 (es5:)getInitialState() 中设置。

componentDidMount

void componentDidMount()

方法在组件初始化渲染 render() 执行之后调用,在组件的生命周期中,仅会被执行一次,在执行这个函数的时候,组件已经拥有 DOM 呈现,可以进行 DOM 的操作,而这个时候,组件已经基本完成的渲染工作,通常可以开始与其他类库交互、操作 DOM 、发起 Ajax 请求、异步加载初始数据、设置事件监听和绑定、设置定时器任务 setTimeout 或者 setInterval 等。

注意:
组件的渲染是递归操作的,因此子组件的这个方法会比父组件的先调用。

组件的更新

当组件已经挂载完毕以后,组件通常已经处于运行阶段,当满足一定条件后,比如 stateprops 发生改变以后,会触发组件的更新操作,组件的更新操作不会再调用组件挂载阶段的两个方法 componentWillMount()componentDidMount() ,在此阶段有四个相关的方会被调用 componentWillReceivePropsshouldComponentUpdatecomponentWillUpdatecomponentDidUpdate

这四个与更新相关的方法,componentWillReceiveProps方法接收到新的属性变更时候被调用,在这里可以根据需要更新本组件的状态 、shouldComponentUpdate则是判断是否出发本组件的更新,默认返回 true 更新组件 、componentWillUpdatecomponentDidUpdate 分别在组件更新前后调用,通常进行组件更新前后的一些准备、处理工作。

props 的改变是来自父组件的状态变更,父组件的变动会引发当前组件的状态变更;而 state 是当前组件通过修改 state 改变当前组件的状态。

注意:
官方推荐把 this.state 当成不可变变量,修改 state 调用 this.setState() 修改。

this.setState()

注意:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
setState() 的操作不是同步的,可以认为是异步的

setState() 执行完毕后,使用 this.state 获取的 state 值很可能仍然是旧的,代码如下:

class Test extends React.Component {
constructor() {
super();
this.state={ value: 0 }
}
componentDidMount() {
console.log("Test componentDidMount");
this.setState({ value: 1 });
console.log("value is: " + this.state.value)
}
render() {
console.log("Test component render");
return null;
}
}
ReactDOM.render(
<Test />,
document.getElementById("react-root")
);

//控制台输出:
Test component render
Test componentDidMount
value is: 0

如果需要在 setState() 后获取更新后的值,可以放在 setState() 回调里:

this.setState(
{ value: 1 },
function() {
console.log("callback value is: " + this.state.value);
}
);

setState(newState) 更新是把 newState 对象的属性直接复制给 this.state

componentWillReceiveProps()

void componentWillReceiveProps(object nextProps)

在组件接收到新的 props 的时候被调用,组件state变化是不会执行这个函数,参数传递过来的是新的 props ,原来的 props 仍然可以通过 this.props 获取,在这里可以根据根据本组件接收到的属性确定是否需要更新本组件的状态。

当在本方法更新 state 时,在调用 shouldComponentUpdate 时,收到新的 state 即为本方法更新后的 state 。

class Test extends React.Component {
constructor() {
super();
this.state={
value: 0
}
}
componentWillReceiveProps(nextProps) {
console.log("This receive new props: " + nextProps.value);
this.setState({
value: nextProps.value
});
console.log("This change state to: " + nextProps.value);
}
shouldComponentUpdate(nextProps, nextState) {
console.log("This receive new state: " + nextState.value);
return true;
}
render() {
return null;
}
}
class Parent extends React.Component {
constructor() {
super();
this.state={ value: 0 }
}
componentDidMount() {
console.log("After 5s, Test component will receive new props");
setTimeout(() => this.setState({ value: 1 }), 5000);
}
render() {
return <Test value={this.state.value} />
}
}
ReactDOM.render(
<Parent />,
document.getElementById("react-root")
);

//控制台输出:
After 5s, Test component will receive new props
This receive new props: 1
This change state to: 1
This receive new state: 1

shouldComponentUpdate()

bool shouldComponentUpdate(object nextProps, object nextState)

在组件在接收到新的 props 或者 state,将要重新渲染之前调用,返回值决定了组件是否更新,如果 shouldComponentUpdate 返回 false,则 render() 将不会执行,直到下一次 state 改变,此外, componentWillUpdatecomponentDidUpdate 也不会被调用。

class Test extends React.Component {
constructor() {
super();
this.state={ value: 0 }
}
componentDidMount() {
setTimeout(() => this.setState({ value: 1 }), 5000);
}
shouldComponentUpdate() {
console.log("componentWillUpdate will be called.");
return false;
}
componentWillUpdate() {
console.log("componentWillUpdate will not be called.");
}
componentDidUpdate() {
console.log("componentDidUpdate will not be called.");
}
render() {
console.log("Test component render");
return null;
}
}
ReactDOM.render(
<Test />,
document.getElementById("react-root")
);

//控制台输出:
Test component render
componentWillUpdate will be called.

由输出结果可以看出,shouldComponentUpdate 返回 false 以后,更新流程不再继续往下走了, componentWillUpdatecomponentDidUpdate 方法也不再执行。

注意:
调用 this.forceUpdate() 函数将不会执行 shouldComponentUpdate 方法,因为 this.forceUpdate() 要求强制更新组件,不会因为受 shouldComponentUpdate 的返回值影响,所以跳过该方法, 但是 componentWillUpdatecomponentDidUpdate 仍然会被调用。另外 this.forceUpdate() 的调用虽然会跳过本组件的 shouldComponentUpdate 方法被执行,对子组件的没有影响。

class Test extends React.Component {
constructor() {
super();
this.state={ value: 0 }
}
//5s后强制更新
componentDidMount() {
setTimeout(() => this.forceUpdate(), 5000);
}
shouldComponentUpdate() {
console.log("shouldComponentUpdate not be called.");
}
componentWillUpdate() {
console.log("componentWillUpdate be called.");
}
componentDidUpdate() {
console.log("componentDidUpdate be called.");
}
render() {
console.log("Test component render");
return null;
}
}
ReactDOM.render(
<Test />,
document.getElementById("react-root")
);

//控制台输出:
Test component render
componentWillUpdate be called.
Test component render
componentDidUpdate be called.

在组件比较多或者比较复杂的项目中,组件的更新成本较大,可以在这里重载默认的逻辑,比较组件的新旧状态和属性值的变化,来确定是否够需要更新组件。

componentWillUpdate()

void componentWillUpdate(object nextProps, object nextState)

如果组件的状态或者属性改变,并且组件 shouldComponentUpdate 返回结果为 true ,此方法会被调用,通常在这这里做一些更新前的准备工作。需要注意的是,这个方法执行完毕后,新的 stateprops 即被更新入 this.statethis.props 中,组件即进入 render 阶段。

注意:
此时组件必然会更新,进入重新render的过程,在此时已经不允许改变组件的状态了。

class Test extends React.Component {
constructor() {
super();
this.state={ value: 0 }
}
componentDidMount() {
setTimeout(() => this.setState({ value: 1 }), 5000);
}
componentWillUpdate() {
this.state={ value: 2 }
console.log("Try to change state.value to 2");
}
render() {
console.log("Test component render");
console.log("Current state.value is: " + this.state.value);
return null;
}
}
ReactDOM.render(
<Test />,
document.getElementById("react-root")
);

//控制台输出:
Test component render
Current state.value is: 0
Try to change state.value to 2
Test component render
Current state.value is: 1

从输出可以看到 componentWillUpdate 方法中调用 this.setState 后,state没有变化,而且也没有再次触发组件的更新操作。

componentDidUpdate()

void componentDidUpdate(object prevProps, object prevState)

组件在更新后执行,在这里已经完成了组件的更新工作了,此时,方法的输入参数变成了更新前的 propsstate , 这这里通常进行一些组件更新后的操作,比如,为新增加的DOM元素绑定事件等。

组件的卸载移除

componentWillUnmount()

void componentWillUnmount()

组件被移除前执行,通常在这里执行一些必要的清理工作,比如清除定时器任务、清除事件监听绑定、清除非 React 创建的 DOM 元素等,代码如下:

class Child extends React.Component {
componentDidMount() {
console.log("Set interval task.");
console.log("Bind event Listener.");
}
componentWillUnmount() {
console.log("Remove interval task.");
console.log("Unbind event Listener.");
}
render() {
console.log("Child component render");
return null;
}
}
class Parent extends React.Component {
constructor() {
super();
this.state={ value: 0 }
}
//5s后修改state,移除Child
componentDidMount() {
setTimeout(() => this.setState({value: 1}), 5000)
}
componentWillUnmount() {
console.log("Remove interval task.");
console.log("Unbind event Listener.");
}
render() {
if (this.state.value == 1) {
console.log("Child component is unmounted.");
return null;
}
else {
console.log("Child component is mounted.");
console.log("After 5s, Child component will be unmounted.");
return <Child />;
}
}
}
ReactDOM.render(
<Parent />,
document.getElementById("react-root")
);

//控制台输出:
Child component is mounted.
After 5s, Child component will be unmounted.
Child component render
Set interval task.
Bind event Listener.
Child component is unmounted.
Remove interval task.
Unbind event Listener.

参考