【連載】淺析React生命週期之二:更新階段

【連載】淺析React生命週期之一:掛載階段css

2、更新階段

react的 UI 組件由 props 和 state 驅動,props接受外部輸入,state 存放內部狀態。react

2.1 先說說外部 props 變化所引發的組件更新。

組件初次掛載,依序執行如下方法。bash

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

爲了演示效果,簡單拆分爲兩個嵌套的父子組件App.jsHeader.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,就實現了以前的效果。很簡潔✌ ️

2.2 再說說內部 state 變化所引發的組件更新。

內部 state 變化引發的組件更新跟外部 props 引發的子組件更新類似,只是內部 state 的變化不會觸發componentWillReceiveProps的執行。

2.3 注意:在componentWillUpdate 中執行 setState所引發的效果,跟在 componenetWillMount 有所區別。

【連載】淺析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!')
    }
  }
複製代碼

注意到,此次即便 componentWillUpdate 循環次數遠遠超過了 react 的限制,應用也沒有報錯奔潰了。
相關文章
相關標籤/搜索