react的 UI 组件由 props 和 state 驱动,props接受外部输入,state 存放内部状态。react
组件初次挂载,依序执行如下方法。bash
为了演示效果,简单拆分为两个嵌套的父子组件App.js
和 Header.js
.dom
如下为 App.jssvg
import React, { Component } from 'react';
import './App.css';
import Header from './Header.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
count:0
};
console.log('App:constructor')
}
componentWillMount(){
console.log('App:componentWillMount')
}
componentDidMount() {
console.log('App:componentDidMount')
console.log('--------------------------------------------')
}
//WARNING! To be deprecated in React v17. Use componentDidUpdate instead.
componentWillUpdate(nextProps, nextState) {
console.log('App:componentWillUpdate')
}
componentDidUpdate(){
console.log('App:componentDidUpdate')
console.log('--------------------------------------------')
}
componentWillReceiveProps(nextProps) {
console.log('App:componentWillReceiveProps')
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
// this.setState({});
}
render() {
console.log('App:render', this.state)
return (
<div className="App"> <Header onClick={this.handleClick} count={this.state.count}/> </div> ); } } export default App; 复制代码
如下为 Header.js函数
import React, { Component, PureComponent } from 'react';
import logo from './logo.svg';
import './Header.css';
class Header extends Component {
constructor(props) {
super(props);
console.log('\tHeader:constructor')
}
componentWillMount(){
console.log('\tHeader:componentWillMount')
}
componentDidMount() {
console.log('\tHeader:componentDidMount')
}
//WARNING! To be deprecated in React v17. Use componentDidUpdate instead.
componentWillUpdate(nextProps, nextState) {
console.log('\tHeader:componentWillUpdate')
}
componentDidUpdate(){
console.log('\tHeader:componentDidUpdate')
}
componentWillReceiveProps(nextProps) {
console.log('\tHeader:componentWillReceiveProps')
}
handleClick = () => {
this.props.onClick()
}
render() {
console.log('\tHeader:render', this.props)
return (
<header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> <div>{this.props.count}次</div> <button onClick={this.handleClick}>+1</button> </header> ); } } export default Header; 复制代码
初始化日志以下,能够清楚得看出父子组件的挂载阶段的钩子函数执行状况:oop
点击+1
按钮,观察日志输出,能够看出外部App 父组件 state.count加1后,Header的 props接收到了 props 的变化的执行状况。post
以上状况很正常,那么若是外部的 handleClick 中不改变 state 的 count 会怎么样呢?子组件会执行更新阶段吗?咱们来试一下。性能
仅App.js的 handleClick 变动和输出日子以下:ui
handleClick = () => {
/*this.setState({ count: this.state.count + 1 });*/
this.setState({});
}
复制代码
从上面能够看出,只要父组件的 render 执行了,子组件就会执行更新阶段。对性能稍微有点要求的场合,及某些须要严格控制父子组件更新阶段的场景,咱们可能须要限制这种状况的发生。
那么,咱们应该怎么作了。对了,就是上文还未说起的 shouldComponentUpdate。咱们能够在子组件 Header 的这个钩子函数中比较 state 和 props 是否变化,只当变化了咱们才容许 Header执行 update。
Header.js添加以下钩子函数,及输出日子以下:
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.count !== this.props.count){
return true;
}
return false;
}
复制代码
能够看出,子组件没有再执行多余的componentWillUpdate、render、componentDidUpdate。
其实,为了简化这种场景的处理,在 react@15.3.0
(July 29, 2016)这个版本中,已经新增了一个 PureComponent 组件。使用这个类不须要本身实现shouldComponentUpdate。如下是改用这个组件类的状况。
Header.js
import React, { PureComponent } from 'react';
import logo from './logo.svg';
import './Header.css';
class Header extends PureComponent {
constructor(props) {
super(props);
console.log('\tHeader:constructor')
}
componentWillMount(){
console.log('\tHeader:componentWillMount')
}
componentDidMount() {
console.log('\tHeader:componentDidMount')
}
//WARNING! To be deprecated in React v17. Use componentDidUpdate instead.
componentWillUpdate(nextProps, nextState) {
console.log('\tHeader:componentWillUpdate')
}
componentDidUpdate(){
console.log('\tHeader:componentDidUpdate')
}
componentWillReceiveProps(nextProps) {
console.log('\tHeader:componentWillReceiveProps')
}
handleClick = () => {
this.props.onClick()
}
render() {
console.log('\tHeader:render', this.props)
return (
<header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> <div>{this.props.count}次</div> <button onClick={this.handleClick}>+1</button> </header> ); } } export default Header; 复制代码
能够看出,咱们把 shouldComponentUpdate 去掉后,将 extends Component
改为了 extends PureComponent
,就实现了以前的效果。很简洁✌ ️
内部 state 变化引发的组件更新跟外部 props 引发的子组件更新类似,只是内部 state 的变化不会触发componentWillReceiveProps的执行。
在【连载】浅析React生命周期之一:挂载阶段 这篇文章中提到过 在componenetWillMount中 setState,新的state 不会引发新的更新行为,可是新的 state内容会被带到 render 中体现。可是在componentWillUpdate则相反,它的下一步原本就是 render,并且新的 state 内容不会被带到 render 中。若是在componentWillUpdate确实设置了新的不一样的 state,则会引发循环的更新行为,若是只是调用了 setState,可是 state 内容并没有变化,则不会引发循环的渲染更新行为。
- componentWillUpdate
- render
- componentDidUpdate
- componentWillUpdate
- render
- componentDidUpdate
- componentWillUpdate
- render
- componentDidUpdate
...
...
复制代码
这么说来,好像有点抽象和模糊,举个例子 🌰。Header 组件改造以下:
import React, { PureComponent } from 'react';
import logo from './logo.svg';
import './Header.css';
class Header extends PureComponent {
constructor(props) {
super(props);
console.log('\tHeader:constructor')
}
componentWillMount(){
console.log('\tHeader:componentWillMount')
}
componentDidMount() {
console.log('\tHeader:componentDidMount')
}
//WARNING! To be deprecated in React v17. Use componentDidUpdate instead.
componentWillUpdate(nextProps, nextState) {
console.log('\tHeader:componentWillUpdate',this.state, nextState)
const { c = 0 } = nextState || {};
this.setState({ c: c + 1 });
}
componentDidUpdate(prevProps, prevState) {
console.log('\tHeader:componentDidUpdate', this.state,prevState)
}
componentWillReceiveProps(nextProps) {
console.log('\tHeader:componentWillReceiveProps')
}
handleClick = () => {
// this.props.onClick()
const { c = 0 } = this.state || {};
this.setState({ c: c + 1 });
}
render() {
// console.log('\tHeader:render', this.props)
console.log('\tHeader:render', this.state)
return (
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
<div>{this.props.count}次</div>
<button onClick={this.handleClick}>+1</button>
</header>
);
}
}
export default Header;
复制代码
那么,是否是就是说咱们不能在 componentWillUpdate 中 setState呢?不少react技术文章中都这样简单绝对得一笔带过,不能!可是。。。现实业务场景是复杂的,有时咱们确实须要在 componentWillUpdate 中更新 dom,而后根据新的 dom 再次调整 dom,以达到最终的展现效果。
其实,若是把 state 控制得当,固然能够在 componentWillUpdate 中 setState。否则,react 库干脆内部直接报错奔溃得了,为什么还要容许咱们去 setState 一下,而后多余得经过循环调用堆栈溢出的方式告知咱们不能这么作?!对吧。
让咱们来尝试一下,将上面的Header.js 的 componentWillUpdate 更改成以下:
componentWillUpdate(nextProps, nextState) {
const SOME_VAL_DEPEND_ON_DOM = 100;
console.log('\tHeader:componentWillUpdate',this.state, nextState)
const { c = 0 } = nextState || {};
if(c < SOME_VAL_DEPEND_ON_DOM){
setTimeout(() => {
this.setState({ c: c + 1 });
},100);
}else{
console.log('\tadjust update finish!')
}
}
复制代码