Вы действительно понимаете жизненный цикл React?

React.js

предисловие

  • Первоначально я думал, что уже знаком с жизненным циклом React и не могу быть с ним более знакомым, пока несколько дней назад, когда я не понял функцию, я не хотел ее, потому что я не полностью понимал ее. Жизненный цикл React, поэтому я подумал об этом и заново изучил жизненный цикл React.

Устаревший жизненный цикл

  • Назначение не будет считаться обновлением во время инициализации, поэтому фаза обновления не будет выполняться.
import React, { Component } from 'react'

export default class LifeCycle extends Component {
    //// props = {age:10,name:'计数器'}
  static defaultProps = {
      name:'计数器'
  }
  constructor(props){
      //Must call super constructor in derived class before accessing 'this' or returning from derived constructor
    super();//this.props = props;
    this.state = {number:0,users:[]};//初始化默认的状态对象
    console.log('1. constructor 初始化 props and state');
  
  }  
  //componentWillMount在渲染过程中可能会执行多次
  componentWillMount(){
    console.log('2. componentWillMount 组件将要挂载');
    //localStorage.get('userss');
  }
  //componentDidMount在渲染过程中永远只有执行一次
  //一般是在componentDidMount执行副作用,进行异步操作
  componentDidMount(){
    console.log('4. componentDidMount 组件挂载完成');
    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 询问组件是否需要更新');
    return true;
  }
  componentWillUpdate(nextProps, nextState){
    console.log('6. componentWillUpdate 组件将要更新');
  }
  componentDidUpdate(prevProps, prevState)){
    console.log('7. componentDidUpdate 组件更新完毕');
  }
  add = ()=>{
      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){
        console.log('SubCounter',nextProps,nextState);
        if(nextProps.number%3==0){
            return true;
        }else{
            return false;
        }
    }
    //componentWillReceiveProp 组件收到新的属性对象
    componentWillReceiveProps(){
      console.log('SubCounter 1.componentWillReceiveProps')
    }
    render(){
        console.log('SubCounter  2.render')
        return(
            <div style={{border:'5px solid green'}}>
                <p>{this.props.number}</p>
            </div>
        )
    }
}

луковая модель

image.png

новый жизненный цикл

static getDerivedStateFromProps

  • static getDerivedStateFromProps(nextProps,prevState): получить переданный от родительского компонентаpropsи предыдущее состояние компонента, возвращающее объект для обновленияstateили вернутьсяnullдля обозначения полученныхpropsНикаких изменений, никаких обновлений не требуетсяstate
  • Что делает этот хук жизненного цикла:передается от родительского компонентаprops картак подкомпонентамstateвыше, так что внутренняя часть компонента не должна проходить черезthis.props.xxxПолучите значение атрибута, унифицированное черезthis.state.xxxПолучать. Сопоставление эквивалентно копированию копии родительского компонента.props, как собственное состояние дочернего компонента. Примечание. Подкомпоненты передаются черезsetStateПри обновлении собственного состояния он не изменяет состояние родительского компонента.props
  • СотрудничатьcomponentDidUpdate, может покрытьcomponentWillReceivePropsвсе виды использования
  • Когда срабатывает хук жизненного цикла:
    • В React 16.3.0: при создании компонента получайте новыеpropsбудет вызван, когда
    • В React 16.4.0: при создании компонента получайте новыеprops, будет вызываться при обновлении состояния компонента
    • онлайн демо—— Протестируйте в версиях 16.3.0 и 16.4.0, при каких обстоятельствах будет срабатывать хук жизненного цикла
  • использовать: онлайн демо
    • Примечание. При получении состояния вам не нужно устанавливать состояние самого компонента.
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: 666
  };

  add = () => {
    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
  };
 	// 根据新的属性对象派生状态对象    
  // nextProps——新的属性对象 prevState——旧的状态对象
  static getDerivedStateFromProps(nextprops, state) {
    console.log('props',nextprops);
    // 返回一个对象来更新 state 或者返回 null 来表示接收到的 props 不需要更新 state 
    if (nextprops.age !== state.age) {
      console.log("更新吧");
      return {
        onChangeParent:nextprops.onChangeParent,
        age: nextprops.age,
        // 注意:这里不需要把组件自身的状态也放进来
        // num:state.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);

getSnapshotBeforeUpdate

  • getSnapshotBeforeUpdate(prevProps, prevState):Получить переданный от родительского компонентаpropsи предыдущее состояние компонента, этот хук жизненного цикла должен иметь возвращаемое значение, которое будет передано в качестве третьего параметра в componentDidUpdate. должен иcomponentDidUpdateиспользовать вместе, иначе будет сообщено об ошибке
  • Когда срабатывает хук жизненного цикла: вызываетсяrenderПосле этого обновитеDOMа такжеrefsДо
  • Что делает этот хук жизненного цикла:позволяет обновить компонентDOMа такжеrefsраньше, отDOMзахватить некоторую информацию (например, положение прокрутки) в
  • СотрудничатьcomponentDidUpdate, может покрытьcomponentWillUpdateвсе виды использования
  • онлайн демо: каждый раз, когда компонент обновляется, получайте предыдущую позицию прокрутки и сохраняйте компонент в предыдущей позиции прокрутки.
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(() => {
      this.setState({
        messages: ["msg:" + this.state.messages.length, ...this.state.messages]
      });
      //this.setState({messages:[...this.state.messages,this.state.messages.length]});
    }, 1000);
  }
  getSnapshotBeforeUpdate() {
    // 返回更新内容的高度 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);

Миграция версии

  • componentWillMount,componentWillReceiveProps,componentWillUpdateПоскольку эти три жизненных цикла часто неправильно понимают и ими злоупотребляют, их называютНебезопасно (не относится к безопасности, но означает, что код, использующий эти жизненные циклы, может иметь ошибки в будущих версиях React, которые могут повлиять на будущий асинхронный рендеринг)жизненный цикл.
  • Реагировать версии 16.3: ввести псевдонимы для небезопасных жизнейUNSAFE_componentWillMount,UNSAFE_componentWillReceivePropsа такжеUNSAFE_componentWillUpdate. (В этом выпуске работают как старые имена жизненного цикла, так и новые псевдонимы.)
  • После реакции 16.3:дляcomponentWillMount,componentWillReceivePropsа такжеcomponentWillUpdateВключить предупреждения об устаревании. (В этом выпуске работают как старые имена времени жизни, так и новые псевдонимы, но старые имена регистрируют предупреждения режима DEV.)
  • Реагировать на версию 17.0:Представляем новый метод рендеринга -Асинхронный рендеринг(асинхронный рендеринг), предлагает жизненный цикл, который можно прервать, а стадия, которую можно прервать, является фактическимdomвиртуальный перед монтированиемdomФаза сборки, то есть три жизненных цикла, которые необходимо удалить.componentWillMount,componentWillReceivePropsа такжеcomponentWillUpdate. (В этом выпуске будет работать только новое имя жизненного цикла «UNSAFE_».)

Общая проблема

когда внешнийpropsПри изменении, как снова выполнять такие операции, как запрос данных, изменение состояния и т.д.

использоватьcomponentWillReceiveProps

class ExampleComponent extends React.Component {
  state = {
    externalData: null,
  };

  componentDidMount() {
    this._loadAsyncData(this.props.id);
  }

  componentWillReceiveProps(nextProps) {
    // 当父组件的 props 改变时,重新请求数据
    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});
      }
    );
  }
}

использоватьgetDerivedStateFromProps + componentDidUpdateСкачать данные

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);
  }
  
  // 借助 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});
      }
    );
  }
}

использоватьgetDerivedStateFromPropsизменить состояние

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 = () => {
    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);

просто используйтеcomponentDidUpdateнаписание

  • не нужно использоватьgetDerivedStateFromPropsилиcomponentWillReceiveProps
  • онлайн демо
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 = () => {
    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);

Используйте ключевые обозначения

  • Путем измененияkey, чтобы повторно инициализировать компонентонлайн демо
  • Звучит медленно, но производительность на данный момент незначительна. Если в обновлении дерева компонентов есть тяжелая логика, это будет быстрее, потому что дочерний компонент опущен.diff
  • Реаги официальный образец
  • Я думаю, что этот способ написания очень подходит: когда вы называете бизнес-пользовательский компонент, написанный коллегой, если он не считает, что внутреннее состояние компонента необходимо следовать за внешнимpropsИзменил ситуацию (не терпится подойти и дать ему коленный молоток 😂😂😂), можно использоватьkeyбыстро реализовать
class ExampleComponent extends React.Component {
  state = {
    id: '123456',
  };
  render(){
    const {id} = this.state;
    // 当 id 变化时,key 也随之改变,那么组件就会重新初始化
    return <ExampleComponent key={id} id={id}/>;
  }
}


class ExampleComponent extends React.Component {
  state = {
    externalData: null,
  };
  // 不需要使用 getDerivedStateFromProps 或者 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});
      }
    );
  }
}

getDerivedStateFromPropsЭто статический метод, и экземпляры компонентов не могут наследовать статические методы, поэтому хук жизненного цикла нельзя использовать с помощьюthisПолучите свойства/методы экземпляра компонента.

  • В некоторых случаях нам нужно выполнять такие операции, как фильтрация/фильтрация данных, передаваемых родительским компонентом, и эти операции обычно выносятся в отдельную функцию (единый принцип), и тогда получается хук жизненного цикла.propsпередаются в эти методы для обработки.
    • Если вы решите поместить эти методы вclassкомпоненты, то эти методы объявляются как статические методы, а затем передаются в хук жизненного циклаclassName.xxxвызвать эти методы.
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){
  	// 过滤数据
    
    ...
    
    return newData;
  }
  
  ...
}

  • или поместите эти методы вclassВне компонента нет необходимости объявлять его как статический метод, и эти методы вызываются непосредственно в хуке жизненного цикла.
function filterFn(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;
  }
 
  ...
}

  • При использовании двух вышеуказанных методов я лично считаю это недостатком: если эти методы более сложные, а другие функции вызываются внутри, то в это время либо все функции обработки объявляются как статические методы, либо упоминаются все методы. вне компонента и должен передаваться слой за слоемpropsстоимость. Не может быть похоже на метод экземпляра компонента, вы можете в каждом методе экземпляра компонента,this.props.xxx / this.state.xxxДоступ к свойствам может быть более проблематичным.
  • Есть еще один способ:комбинироватьcomponentDidUpdateиспользоватьонлайн демо
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 = () => {
    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 !== state.age) {
      return {
        // 给一个标识
        status: false,
        // age: nextprops.age,
        onChangeParent: nextprops.onChangeParent
      };
    }
    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);

использоватьgetDerivedStateFromPropsПри получении состояния вам не нужно устанавливать состояние самого компонента

class AAA extends React.Component {
  // 必须给 state 设置一个值,哪怕是一个空对象
  state = {
  	num:666
  };
  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.id !== prevState.prevId) {
      return {
        data:nextProps.data,
        prevId: nextProps.id,
        // 只需要映射属性,不需要把组件自身的状态也加进去
        // num:prevState.num
      };
    }
    return null;
  }
 
  ...
}

еслиsetStateОбновленное значение не меняется, так что эти хуки жизненного цикла все еще срабатывают?

  • Даже если каждый раз устанавливается одно и то же значение, обновление все равно будет запускаться.
import React, {Component} from 'react'

export default class LifeCycle extends Component {
    static defaultProps = {
        name: '计数器'
    };

    constructor(props) {
        super(props);
        this.state = {number: 0};//初始化默认的状态对象
        console.log('1. constructor 初始化 props and state');
    }
    
    componentWillMount() {
        console.log('2. componentWillMount 组件将要挂载');
    }
    
    componentDidMount() {
        console.log('4. componentDidMount 组件挂载完成');
    }

    shouldComponentUpdate(nextProps, nextState) {
        console.log('Counter', nextProps, nextState);
        console.log('5. shouldComponentUpdate 询问组件是否需要更新');
        return true;
    }

    componentWillUpdate() {
        console.log('6. componentWillUpdate 组件将要更新');
    }

    componentDidUpdate() {
        console.log('7. componentDidUpdate 组件更新完毕');
    }

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

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

УходитеcomponentWillMountдобавить прослушиватель событий

  • существуетcomponentDidMountдобавить прослушиватель событий
  • componentWillMountОн может быть прерван или вызван несколько раз, поэтому нет гарантии, что прослушиватель событий может быть успешно размонтирован во время размонтирования, что может вызвать утечку памяти.

В связи с введением асинхронного рендеринга в будущей версии React,domСтадии перед монтированием могут быть прерваны и перезапущены, что приведет кcomponentWillMount,componentWillUpdate,componentWillReceivePropsмогут запускаться несколько раз в обновлении, поэтому те побочные эффекты, которые должны запускаться только один раз, должны быть помещены вcomponentDidMountсередина

  • Вот почему асинхронный запрос помещается вcomponentDidMount, а не вcomponentWillMountпо причинам в , для обратной совместимости

Самое распространенное заблуждение заключается в том, чтоgetDerivedStateFromPropsа такжеcomponentWillReceivePropsтолько вpropsВызывается только при "изменении". На самом деле, пока родительский компонент выполняет повторный рендеринг, эти две функции жизненного цикла будут вызываться снова, независимо от того,propsЕсть ли «изменение»

Ссылаться на

Update on Async Rendering

React v16.9.0 and the Roadmap Update

Вам, вероятно, не нужно использовать производное состояние

Рекомендуемое чтение

Cookie, Session, Token, JWT, которые невозможно отличить по глупости

Подробные хуки React [почти 1W слов] + бой проекта

Подробный React SSR [почти 1W слов] + 2 актуальных проекта

Реализация простого веб-пакета от 0 до 1

Webpack транспилирует существующие решения Typescript