preface

  • I thought I was too familiar with the React life cycle until a few days ago, when I implemented a function, I failed to fully understand the React life cycle, so I relearned the React life cycle

The old version life cycle

  • Assignment is not counted as an update when initialized, so no update phase is performed
import React, { Component } from 'react'

export default class LifeCycle extends Component {
    //// props = {age:10,name:' counter '}
  static defaultProps = {
      name:'counter'
  }
  constructor(props){
      //Must call super constructor in derived class before accessing 'this' or returning from derived constructor
    super(a);//this.props = props;
    this.state = {number:0.users: []};// Initialize the default state object
    console.log('1. Constructor initializing props and state');
  
  }  
  //componentWillMount may be executed multiple times during rendering
  componentWillMount(){
    console.log('2. ComponentWillMount component to be mounted ');
    //localStorage.get('userss');
  }
  //componentDidMount is always executed once during rendering
  ComponentDidMount () {componentDidMount ()
  componentDidMount(){
    console.log('4. ComponentDidMount completed ');
    fetch('https://api.github.com/users').then(res= >res.json()).then(users= >{
        console.log(users);
        this.setState({users});
    });
  }
  shouldComponentUpdate(nextProps,nextState){
    console.log('Counter',nextProps,nextState);
    console.log('5. ShouldComponentUpdate asks if component needs to be updated ');
    return true;
  }
  componentWillUpdate(nextProps, nextState){
    console.log('6. ComponentWillUpdate component to be updated ');
  }
  componentDidUpdate(prevProps, prevState)){
    console.log('7. ComponentDidUpdate component updated ');
  }
  add = (a)= >{
      this.setState({number:this.state.number});
  };
  render() {
    console.log('3. Render ')
    return (
      <div style={{border:'5px solid red',padding:'5px'}} >
        <p>{this.props.name}:{this.state.number}</p>
        <button onClick={this.add}>+</button>
        <ul>
            {
                this.state.users.map(user=>(<li>{user.login}</li>))}</ul>
        {this.state.number%2==0&&<SubCounter number={this.state.number}/>}
      </div>) } } class SubCounter extends Component{ constructor(props){ super(props); this.state = {number:0}; } componentWillUnmount(){ console.log('SubCounter componentWillUnmount'); } shouldComponentUpdate(nextProps,nextState){shouldProps (props,nextState){ console.log('SubCounter',nextProps,nextState); if(nextProps.number%3==0){ return true; }else{ return false; }} / / componentWillReceiveProp components received new attribute object componentWillReceiveProps () {the console. The log (' SubCounter 1.componentWillReceiveProps') } render(){ console.log('SubCounter 2.render') return(<div style={{border:'5px solid green'}} >
                <p>{this.props.number}</p>
            </div>)}}Copy the code

The onion model

New edition life cycle

static getDerivedStateFromProps

  • static getDerivedStateFromProps(nextProps,prevState): Received from the parent componentpropsAnd the previous state of the component, and returns an object to updatestateOr returnsnullTo represent the receivedpropsNo changes, no updatesstate
  • The lifecycle hook does:To pass in the parent componentprops mappingTo the child componentstateAbove, so that the inside of the component doesn’t have to pass throughthis.props.xxxGot the value of the property. All passthis.state.xxxTo obtain. The mapping is a copy of the parent componentprops, as the child component’s own state. Note: Child components passsetStateThe parent component does not change when updating its stateprops
  • Cooperate withcomponentDidUpdate, can covercomponentWillReceivePropsAll usages of
  • When the lifecycle hook fires:
    • In React 16.3.0: The component is instantiated and new is receivedpropsWill be called
    • In React 16.4.0: The component is instantiated and new is receivedpropsIs called when the component status updates
    • Online demo – Test in versions 16.3.0 and 16.4.0 when the lifecycle hook is fired
  • Use: The online demo
    • Note: When deriving state, you do not need to set the state of the component itself
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

function App() {
  return (
    <div className="App">
      <AAA />
    </div>
  );
}

class AAA extends React.Component {
  state = {
    age: Awesome!
  };

  add = (a)= > {
    this.setState({ age: this.state.age + 1 });
  };

  render() {
    return (
      <div>
        <ChildA onChangeParent={this.add} age={this.state.age} />
      </div>); } } class ChildA extends React.Component { state = { num: 888 }; // Derive the state object from the new property object // nextProps -- prevState -- static getDerivedStateFromProps(nextProps, state) { console.log('props',nextprops); // Return an object to update state or null to indicate that the props received do not need to update state if (nextprops. Age! == state.age) {console.log(" update "); Return {onChangeParent: nextprops onChangeParent, age: nextprops. Age, / / note: there is no need to put the state of the component itself also come in / / num: state. The num}; } return null; } add = () => { this.setState({ num: this.state.num + 1 }); }; render() { const { onChangeParent } = this.state; console.log('state',this.state); return (<>
        <div onClick={onChangeParent}>change</div>
        <div onClick={this.add}>add</div>
      </>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Copy the code

getSnapshotBeforeUpdate

  • getSnapshotBeforeUpdate(prevProps, prevState):Received from the parent componentpropsThe lifecycle hook must have a return value, which will be passed as the third argument componentDidUpdate. Must andcomponentDidUpdateOtherwise, an error will be reported
  • When the lifecycle hook fires: is called forrenderAfter, updateDOMrefsbefore
  • The lifecycle hook does:It allows you to update componentsDOMrefsBefore, fromDOMCapture some information (such as scroll position) in
  • Cooperate withcomponentDidUpdate, can covercomponentWillUpdateAll usages of
  • Online demo: Every time a component is updated, get the previous scroll position and keep the component at the previous scroll position
import React, { Component } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  return (
    <div className="App">
      <GetSnapshotBeforeUpdate />
    </div>
  );
}

class GetSnapshotBeforeUpdate extends Component {
  constructor(props) {
    super(props);
    this.wrapper = React.createRef();
    this.state = { messages: []}; } componentDidMount() { setInterval((a)= > {
      this.setState({
        messages: ["msg:" + this.state.messages.length, ... this.state.messages] });//this.setState({messages:[...this.state.messages,this.state.messages.length]});
    }, 1000);
  }
  getSnapshotBeforeUpdate() {
    // Return the height of the updated content 300px
    return this.wrapper.current.scrollHeight;
  }
  componentDidUpdate(prevProps, prevState, prevScrollHeight) {
    this.wrapper.current.scrollTop =
      this.wrapper.current.scrollTop +
      (this.wrapper.current.scrollHeight - prevScrollHeight);
  }
  render() {
    let style = {
      height: "100px".width: "200px".border: "1px solid red".overflow: "auto"
    };
    return (
      <ul style={style} ref={this.wrapper}>
        {this.state.messages.map((message, index) => (
          <li key={index}>{message}</li>
        ))}
      </ul>); }}const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Copy the code

Version of the migration

  • componentWillMount.componentWillReceiveProps.componentWillUpdateThese three life cycles are called because they are often misunderstood and abusedUnsafe (not secure, but code that uses these life cycles may have bugs in future React releases that could affect future asynchronous rendering)Life cycle of.
  • The React version 16.3: Introduces aliases for unsafe lifecyclesUNSAFE_componentWillMount.UNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate. (Both old lifecycle names and new aliases are available in this release)
  • React 16.3 and later: in order tocomponentWillMount.componentWillReceivePropscomponentWillUpdateEnable deprecation warnings. (Both the old lifecycle name and the new alias are available in this release, but the old name logs the DEV mode warnings)
  • React 17.0 version:Introduced new rendering methods —Asynchronous rendering(Async Rendering), proposing a life cycle that can be interrupted, and the phase that can be interrupted is realitydomVirtual before mountdomThe construction phase, the three life cycles to be removedcomponentWillMount.componentWillReceivePropscomponentWillUpdate. (Starting with this release, only the new “UNSAFE_” life cycle name will be in effect)

Q&A

When an externalpropsHow do you request data again, change state, and so on when it changes

usecomponentWillReceiveProps

class ExampleComponent extends React.Component {
  state = {
    externalData: null}; componentDidMount() {this._loadAsyncData(this.props.id);
  }

  componentWillReceiveProps(nextProps) {
    // Request data again when the props of the parent component changes
    if(nextProps.id ! = =this.props.id) {
      this.setState({externalData: null});
      this._loadAsyncData(nextProps.id);
    }
  }

  componentWillUnmount() {
    if (this._asyncRequest) {
      this._asyncRequest.cancel();
    }
  }

  render() {
    if (this.state.externalData === null) {
      // Render loading state ...
    } else {
      // Render real UI ...
    }
  }

  _loadAsyncData(id) {
    this._asyncRequest = asyncLoadData(id).then(
      externalData= > {
        this._asyncRequest = null;
        this.setState({externalData}); }); }}Copy the code

usegetDerivedStateFromProps + componentDidUpdateLoad the data

class ExampleComponent extends React.Component {
  state = {
    externalData: null};static getDerivedStateFromProps(nextProps, prevState) {
    if(nextProps.id ! == prevState.prevId) {return {
        externalData: null.prevId: nextProps.id,
      };
    }
    return null;
  }

  componentDidMount() {
    this._loadAsyncData(this.props.id);
  }
  
  / / use componentDidUpdate
  componentDidUpdate(prevProps, prevState) {
    if (this.state.externalData === null) {
      this._loadAsyncData(this.props.id);
    }
  }

  componentWillUnmount() {
    if (this._asyncRequest) {
      this._asyncRequest.cancel();
    }
  }

  render() {
    if (this.state.externalData === null) {
      // Render loading state ...
    } else {
      // Render real UI ...
    }
  }

  _loadAsyncData(id) {
    this._asyncRequest = asyncLoadData(id).then(
      externalData= > {
        this._asyncRequest = null;
        this.setState({externalData}); }); }}Copy the code

usegetDerivedStateFromPropsChange the states

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  return (
    <div className="App">
      <AAA />
    </div>
  );
}

class AAA extends React.Component {
  state = {
    age: 66
  };

  add = (a)= > {
    this.setState({ age: this.state.age + 1 });
  };
  render() {
    return (
      <div>
        <ChildA onChangeParent={this.add} age={this.state.age} />
      </div>); } } class ChildA extends React.Component { state = { num: 88 }; static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.age ! == prevState.age) { return { age: nextProps.age }; } return null; } add = () => { this.setState({ num: this.state.num + 1 }); }; render() { const { onChangeParent } = this.props; console.log("render", this.state); return (<>
        <div onClick={onChangeParent}>change</div>
        <div onClick={this.add}>add</div>
      </>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Copy the code

onlycomponentDidUpdateThe writing of

  • It doesn’t have to begetDerivedStateFromPropsorcomponentWillReceiveProps
  • The online demo
import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  return (
    <div className="App">
      <AAA />
    </div>
  );
}

class AAA extends React.Component {
  state = {
    age: 66
  };

  add = (a)= > {
    this.setState({ age: this.state.age + 1 });
  };
  render() {
    return (
      <div>
        <ChildA onChangeParent={this.add} age={this.state.age} />
      </div>); } } class ChildA extends React.Component { state = { num: 88, age: this.props.age }; add = () => { this.setState({ num: this.state.num + 1 }); }; componentDidUpdate() { if (this.props.age ! == this.state.age) { console.log("componentDidUpdate", this.props.age); this.setState({ age: this.props.age }); } } render() { const { onChangeParent } = this.props; console.log("render", this.state); return (<>
        <div onClick={onChangeParent}>change</div>
        <div onClick={this.add}>add</div>
      </>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Copy the code

Use key

class ExampleComponent extends React.Component {
  state = {
    id: '123456'}; render(){const {id} = this.state;
    // When the ID changes, the key changes, and the component is reinitialized
    return <ExampleComponent key={id} id={id}/>; } } class ExampleComponent extends React.Component { state = { externalData: null, }; / / don't need to use getDerivedStateFromProps or componentWillReceiveProps / / static getDerivedStateFromProps (nextProps, prevState) { // if (nextProps.id ! == prevState.prevId) { // return { // externalData: null, // prevId: nextProps.id, // }; // } // return null; // } componentDidMount() { this._loadAsyncData(this.props.id); } componentWillUnmount() { if (this._asyncRequest) { this._asyncRequest.cancel(); } } render() { if (this.state.externalData === null) { // Render loading state ... } else { // Render real UI ... } } _loadAsyncData(id) { this._asyncRequest = asyncLoadData(id).then( externalData => { this._asyncRequest = null; this.setState({externalData}); }); }}Copy the code

getDerivedStateFromPropsIs a static method, and component instances cannot inherit static methods, so the lifecycle hook cannot be used internallythisGets properties/methods of the component instance.

  • In some cases, we need to filter/filter the data passed by the parent component, and these operations are usually put in a separate function (single principle) and retrieved by the lifecycle hookpropsPassed into these methods for processing.
    • If you choose to put these methods inclassComponent, then these methods must be declared static and passed through the lifecycle hookclassName.xxxCall these methods.
class AAA extends React.Component {

  static getDerivedStateFromProps(nextProps, prevState) {
    if(nextProps.id ! == prevState.prevId) {const data = AAA.filterFn(nextProps.data);
      return {
        data,
        prevId: nextProps.id,
      };
    }
    return null;
  }
  
  static filterFn(data){
  	// Filter data. return newData; }... }Copy the code
  • Or put these methods inclassInstead of declaring static methods outside the component, these methods are called directly from the lifecycle hook.
function filterFn(data){
  	// Filter data. return newData; }class AAA extends React.Component {

  static getDerivedStateFromProps(nextProps, prevState) {
    if(nextProps.id ! == prevState.prevId) {const data = filterFn(nextProps.data);
      return {
        data,
        prevId: nextProps.id,
      };
    }
    return null; }... }Copy the code
  • One drawback I personally see with both methods is that if the methods are complex and other functions are called internally, either all the handlers are explicitly static or all the methods are external to the component and need to be passed down one layer after anotherpropsValue. It cannot be passed within each component instance method, as is the case with component instance methodsthis.props.xxx / this.state.xxxAccessing properties can be a bit of a hassle.
  • There’s another way:In combination withcomponentDidUpdateuseThe online demo
import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  return (
    <div className="App">
      <AAA />
    </div>
  );
}

class AAA extends React.Component {
  state = {
    age: 66
  };

  add = (a)= > {
    this.setState({ age: this.state.age + 1 });
  };
  render() {
    return (
      <div>
        <ChildA onChangeParent={this.add} age={this.state.age} />
      </div>); } } class ChildA extends React.Component { state = { num: 88 }; static getDerivedStateFromProps(nextprops, state) { console.log("getDerivedStateFromProps", nextprops); if (nextprops.age ! Age) {return {// give an identity status: false, // age: nextprops. Age, onChangeParent: nextprops. } return null; } add = () => { this.setState({ num: this.state.num + 1 }); }; processData(){ console.log("process",this.props); return this.props.age; } componentDidUpdate() {// If (! this.state.status) { this.setState({ age: this.processData(), status: true }); console.log("componentDidUpdate"); } } componentDidMount() { this.setState({ age: this.props.age, status: true }); } render() { const { onChangeParent } = this.state; console.log("render", this.state); return (<>
        <div onClick={onChangeParent}>change</div>
        <div onClick={this.add}>add</div>
      </>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Copy the code

usegetDerivedStateFromPropsWhen deriving state, you do not need to set the state of the component itself

class AAA extends React.Component {
  // State must be set to a value, even for an empty object
  state = {
  	num:Awesome!
  };
  static getDerivedStateFromProps(nextProps, prevState) {
    if(nextProps.id ! == prevState.prevId) {return {
        data:nextProps.data,
        prevId: nextProps.id,
        // You only need to map attributes. You do not need to add the state of the component itself
        // num:prevState.num
      };
    }
    return null; }... }Copy the code

ifsetStateThe updated values remain the same, so will these lifecycle hooks still fire?

  • Even if you set the same value every time, the update will still be triggered
import React, {Component} from 'react'

export default class LifeCycle extends Component {
    static defaultProps = {
        name: 'counter'
    };

    constructor(props) {
        super(props);
        this.state = {number: 0};// Initialize the default state object
        console.log('1. Constructor initializing props and state');
    }
    
    componentWillMount() {
        console.log('2. ComponentWillMount component to be mounted ');
    }
    
    componentDidMount() {
        console.log('4. ComponentDidMount completed ');
    }

    shouldComponentUpdate(nextProps, nextState) {
        console.log('Counter', nextProps, nextState);
        console.log('5. ShouldComponentUpdate asks if component needs to be updated ');
        return true;
    }

    componentWillUpdate() {
        console.log('6. ComponentWillUpdate component to be updated ');
    }

    componentDidUpdate() {
        console.log('7. ComponentDidUpdate component updated ');
    }

    add = (a)= > {
        this.setState({number: this.state.number });
    };

    render() {
        console.log('(3) render rendering')
        return (
            <div style={{border: '5px solid red', padding: '5px'}} >
                <p>{this.state.number}</p>
                <button onClick={this.add}>+</button>
            </div>)}}Copy the code

Don’t incomponentWillMountAdd event listeners to

  • incomponentDidMountAdd event listeners to
  • componentWillMountCan be interrupted or called multiple times, so there is no guarantee that event listeners will be unmounted successfully and may cause memory leaks

As future versions of React roll out asynchronous rendering, indomStages prior to being mounted can be interrupted and restarted, resulting incomponentWillMount,componentWillUpdate,componentWillReceivePropsIt is possible to fire multiple times in an update, so side effects that only want to fire once should be placed incomponentDidMount

  • That’s why you put asynchronous requests incomponentDidMountIn, not incomponentWillMountFor backward compatibility

The most common misconception is thatgetDerivedStateFromPropscomponentWillReceivePropsOnly inpropsOnly called when “changed”. In fact, these two lifecycle functions are called again whenever the parent component is re-rendered, regardlesspropsIs there a “change”

reference

Update on Async Rendering

React v16.9.0 and the Roadmap Update

You may not need to use derived state

Recommended reading

Cookie, Session, Token, JWT

React Hooks 【 nearly 1W words 】+ project combat

React SSR: + 2 projects

Implement a simple Webpack from 0 to 1

Webpack translates existing Typescript schemas