Связь между хуками и жизненным циклом React

внешний интерфейс

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

Жизненный цикл React известен многим, но обычно все, что мы знаем, этоодин компонентжизненный цикл, но дляКомпонент хуков, несколько связанных компонентовКаков жизненный цикл (родительско-дочерние компоненты и одноуровневые компоненты)? Вы думали об этом и поняли?Далее у нас будет полное представление о жизненном цикле React.

окомпоненты, мы имеем в виду здесьReact.Componentтак же какReact.PureComponent, но включает ли он компонент Hooks?

1. Компонент крючков

функциональный компонентСуть - функция, понятия состояния нет, поэтомунет жизненного циклаОдним словом, простофункция рендерингаВот и все.

но импортироватьHooksТогда он становится другим, он позволяет компонентам использовать состояние и другие возможности React без использования классов, и он ближе к реализации синхронизации состояния, чем к жизненному циклу класса, чем к жизненному циклу события ответа. но мы можем использоватьuseState,useEffect()а такжеuseLayoutEffect()смоделировать жизненный цикл реализации.

который:Компоненты хуков ближе к реализации синхронизации состояния, чем к реагированию на события жизненного цикла..

Ниже приведен конкретный жизненный цикл и хукиПереписка:

  • constructor: Функциональные компоненты не нуждаются в конструкторе, мы можем сделать это, вызвавuseStateдля инициализации состояния. Если вычисления дорогие, вы также можете передать функцию вuseState.

    const [num, UpdateNum] = useState(0)
    
  • getDerivedStateFromProps: В общем, нам не нужно его использовать, мы можемОбновление состояния во время рендеринга, чтобы достичьgetDerivedStateFromPropsцель.

    function ScrollView({row}) {
      let [isScrollingDown, setIsScrollingDown] = useState(false);
      let [prevRow, setPrevRow] = useState(null);
    
      if (row !== prevRow) {
        // Row 自上次渲染以来发生过改变。更新 isScrollingDown。
        setIsScrollingDown(prevRow !== null && row > prevRow);
        setPrevRow(row);
      }
    
      return `Scrolling down: ${isScrollingDown}`;
    }
    

    React немедленно завершит первый рендеринг и повторно запустит компонент с обновленным состоянием, чтобы избежать слишком больших затрат производительности.

  • shouldComponentUpdate:Можно использовать React.memoобернуть компонент в егоpropsпроводить поверхностные сравнения

    const Button = React.memo((props) => {
      // 具体的组件
    });
    

    Уведомление:React.memo равно PureComponent, который только поверхностно сравнивает реквизит. здесь тоже можно использоватьuseMemoОптимизируйте каждый узел.

  • render: это само тело функционального компонента.

  • componentDidMount, componentDidUpdate:useLayoutEffectФаза вызова такая же, как и у них обоих. Тем не менее, мы рекомендуем вамСначала используйте useEffect, и пытайтесь использовать его только в случае неудачиuseLayoutEffect.useEffectКомбинация всего этого может быть выражена.

    // componentDidMount
    useEffect(()=>{
      // 需要在 componentDidMount 执行的内容
    }, [])
    
    useEffect(() => { 
      // 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容
      document.title = `You clicked ${count} times`; 
      return () => {
        // 需要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,遵守先清理后更新)
        // 以及 componentWillUnmount 执行的内容       
      } // 当函数中 Cleanup 函数会按照在代码中定义的顺序先后执行,与函数本身的特性无关
    }, [count]); // 仅在 count 更改时更新
    

    Помните, что React будет ждать, пока браузер завершит отрисовку экрана, прежде чем отложить вызов.useEffect, что делает дополнительные операции удобными

  • componentWillUnmount: эквивалентноuseEffectвернулся внутрьcleanupфункция

    // componentDidMount/componentWillUnmount
    useEffect(()=>{
      // 需要在 componentDidMount 执行的内容
      return function cleanup() {
        // 需要在 componentWillUnmount 执行的内容      
      }
    }, [])
    
  • componentDidCatch and getDerivedStateFromError:В настоящее времяЕще нетЭквиваленты хуков для этих методов, но скоро будут добавлены.

Для удобства памяти они примерно сведены в таблицу следующим образом.

компонент класса Крючки
constructor useState
getDerivedStateFromProps функция обновления в useState
shouldComponentUpdate useMemo
render сама функция
componentDidMount useEffect
componentDidUpdate useEffect
componentWillUnmount Функция, возвращаемая в useEffect
componentDidCatch без
getDerivedStateFromError без

Во-вторых, жизненный цикл одного компонента

1. Жизненный цикл

До версии 16.3

Мы можем разделить жизненный цикл на три фазы:

  • горная сцена
  • Этап обновления компонента
  • этап удаления

Отдельно:

  1. горная сцена
    • constructor: Избегайте копирования значения реквизита в состояние
    • componentWillMount
    • render: Самый важный шаг реакции, создание виртуального дома, выполнение алгоритма сравнения и обновление дерева домов выполняются здесь.
    • componentDidMount
  2. Этап обновления компонента
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • render
    • componentDidUpdate
  3. этап удаления
    • componentWillUnMount

Есть проблема с этим жизненным циклом, то есть при обновлении компонентов верхнего уровня сложных компонентов стек вызовов будет очень длинным, при выполнении сложных операций основной поток может быть заблокирован на длительное время, в результате чего плохой пользовательский опыт. ,FiberОн родился, чтобы решить эту проблему.

После версии 16.3

Fiber — это, по сути, кадр виртуального стека, и новый планировщик будет свободно планировать эти кадры в соответствии с приоритетом, тем самым изменяя предыдущий синхронный рендеринг на асинхронный рендеринг и вычисляя обновления в сегментах, не влияя на работу.

Для асинхронного рендеринга есть две фазы:

  • reconciliation:
    • componentWillMount
    • componentWillReceiveProps
    • shouldConmponentUpdate
    • componentWillUpdate
  • commit
    • componentDidMount
    • componentDidUpdate

в,reconciliationФазы могут быть прерваны, поэтомуreconcilationФункция, выполняемая на этапе, будет вызываться несколько раз, очевидно, что это неразумно.

Итак, V16.3 представил новый API для решения этой проблемы:

  1. static getDerivedStateFromProps: Функция находится вЭтап монтирования и этап обновления компонентовбудет выполнено, т.е.Каждый раз получайте новыйpropsилиstateбудет выполнен после,вместо этого используется на этапе монтированияcomponentWillMount; сотрудничать на этапе обновления компонентаcomponentDidUpdate, может покрытьcomponentWillReceivePropsвсе виды использования .

    В то же время это статическая функция, поэтому доступ к телу функции невозможен.this, будет основываться наnextPropsа такжеprevStateРассчитывается ожидаемое изменение состояния, и возвращенный результат отправляется вsetState,вернутьnullэто означает, что обновление не требуетсяstate, и этот возвратнеобходимо.

  2. getSnapshotBeforeUpdate: функция будет вrenderПосле, до обновления DOMВызывается для чтения последних данных DOM.

    возвращает значение,так какcomponentDidUpdateтретий параметр;сотрудничатьcomponentDidUpdate, может покрытьcomponentWillUpdateвсе виды использования .

Примечание. V16.3 используется только для монтажа компонентов или компонентов.propsБудет вызван процесс обновления, то есть если он инициирован собственным setState или forceUpdate, а не родительским компонентом, тоstatic getDerivedStateFromPropsтоже не будет вызываться, исправлено на вызов обоих в V16.4.

То есть обновленный жизненный цикл это:

  1. горная сцена
    • constructor
    • static getDerivedStateFromProps
    • render
    • componentDidMount
  2. фаза обновления
    • static getDerivedStateFromProps
    • shouldComponentUpdate
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate
  3. этап удаления
    • componentWillUnmount

2. Жизненный цикл, непонимание

Непонимание одно:getDerivedStateFromProps а также componentWillReceivePropsтолько вprops Изменятьбудет вызван, когда

Фактически,Как только родитель перерендерится,getDerivedStateFromProps а также componentWillReceivePropsбудет вызван снова, независимо отpropsБыли ли какие-либо изменения. Поэтому небезопасно напрямую назначать реквизиты для состояния в этих двух методах.

// 子组件
class PhoneInput extends Component {
  state = { phone: this.props.phone };

  handleChange = e => {
    this.setState({ phone: e.target.value });
  };

  render() {
    const { phone } = this.state;
    return <input onChange={this.handleChange} value={phone} />;
  }

  componentWillReceiveProps(nextProps) {
    // 不要这样做。
    // 这会覆盖掉之前所有的组件内 state 更新!
    this.setState({ phone: nextProps.phone });
  }
}

// 父组件
class App extends Component {
  constructor() {
    super();
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    // 使用了 setInterval,
    // 每秒钟都会更新一下 state.count
    // 这将导致 App 每秒钟重新渲染一次
    this.interval = setInterval(
      () =>
        this.setState(prevState => ({
          count: prevState.count + 1
        })),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {
    return (
      <>
        <p>
          Start editing to see some magic happen :)
        </p>
        <PhoneInput phone='call me!' /> 
        <p>
          This component will re-render every second. Each time it renders, the
          text you type will be reset. This illustrates a derived state
          anti-pattern.
        </p>
      </>
    );
  }
}

Примеры можно найти здесь

Конечно, мы можем сделать это в приложении родительского компонента.shouldComponentUpdateСравните, изменено ли электронное письмо реквизитов, а затем решите, нужно ли повторно отображать, но если дочерний компонент принимает несколько реквизитов (более сложный), это трудно обработать, иshouldComponentUpdateОн в основном используется для повышения производительности, и разработчикам не рекомендуется использоватьshouldComponetUpdate(можно использоватьReact.PureComponet).

Мы также можем использоватьИзменить состояние после изменения реквизита.

class PhoneInput extends Component {
  state = {
    phone: this.props.phone
  };

  componentWillReceiveProps(nextProps) {
    // 只要 props.phone 改变,就改变 state
    if (nextProps.phone !== this.props.phone) {
      this.setState({
        phone: nextProps.phone
      });
    }
  }
  
  // ...
}

Но это также приведет к проблеме: когда реквизиты более сложные, отношения между реквизитами и состоянием нелегко контролировать, что может привести к проблемам.

Решение первое:Полностью управляемые компоненты

function PhoneInput(props) {
  return <input onChange={props.onChange} value={props.phone} />;
}

Полностью контролируется реквизитами, состояние не выводится

Решение второе:Неуправляемые компоненты с ключами

class PhoneInput extends Component {
  state = { phone: this.props.defaultPhone };

  handleChange = event => {
    this.setState({ phone: event.target.value });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.phone} />;
  }
}

<PhoneInput
  defaultPhone={this.props.user.phone}
  key={this.props.user.id}
/>

когда keyКогда он изменится, ReactСоздайте новый компонент вместо обновления существующего

Недоразумение 2: скопировать значение реквизита непосредственно в состояние

Избегайте копирования значения реквизита в состояние

constructor(props) {
 super(props);
 // 千万不要这样做
 // 直接用 props,保证单一数据源
 this.state = { phone: props.phone };
}

3. Порядок выполнения нескольких компонентов

1. Родительско-дочерние компоненты

  • горная сцена

    Минутадвасцена:

    • первыйодинФаза, от родительского компонента к собственномуrender, проанализируйте, какие подкомпоненты нужно отрисовывать под него, и проанализируйте, какие подкомпонентыСинхронизированные дочерние компонентычтобы создать, нажмитерекурсивный порядокВыполняйте каждый подкомпонент один за другим, чтобыrender, создайте дерево Virtual DOM, соответствующее родительскому и дочернему компонентам, и зафиксируйте его в DOM.
    • первыйдваНа этом этапе DOM-узел сгенерирован, монтирование компонента завершено и начинается последующий процесс. Сначала запускайте соответствующие подкомпоненты синхронизации по очереди.componentDidMount, который, наконец, запускает родительский компонент.

    Уведомление: если родительский компонент содержит асинхронный дочерний компонент, он будет создан после монтирования родительского компонента.

    Итак, порядок выполнения такой:

    Родительский компонент getDerivedStateFromProps —> синхронный дочерний компонент getDerivedStateFromProps —> синхронный дочерний компонент componentDidMount —> родительский компонент componentDidMount —> асинхронный дочерний компонент getDerivedStateFromProps —> асинхронный дочерний компонент componentDidMount

  • фаза обновления

    React разработан, чтобы следовать модели одностороннего потока данных., то есть данные передаются от родительского компонента к дочернему компоненту.

    • первыйодинфаза, которая начинается с родительского компонента, выполняется

      1. static getDerivedStateFromProps
      2. shouldComponentUpdate

      обновить до собственногоrender, проанализировать, какие подкомпоненты нужно отрисовывать под него, иПодсборкачтобы создать, нажмитерекурсивный порядокВыполняйте каждый подкомпонент один за другим, чтобыrender, создайте дерево Virtual DOM, соответствующее родительскому и дочернему компонентам, и сравните его с существующим деревом Virtual DOM, чтобы вычислитьЧасть виртуального DOM, которая действительно меняется, и только собственные манипуляции с DOM для этой части.

    • первыйдваНа этом этапе DOM-узел сгенерирован, монтирование компонента завершено и начинается последующий процесс. Сначала поочередно запускайте следующие функции синхронизированных дочерних компонентов и, наконец, запускайте родительский компонент.

      1. getSnapshotBeforeUpdate()
      2. componentDidUpdate()

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

      Итак, порядок выполнения такой:

      Родительский компонент getDerivedStateFromProps —> родительский компонент shouldComponentUpdate —> дочерний компонент getDerivedStateFromProps —> дочерний компонент shouldComponentUpdate —> дочерний компонент getSnapshotBeforeUpdate —> родительский компонент getSnapshotBeforeUpdate —> дочерний компонент componentDidUpdate —> родительский компонент componentDidUpdate

  • этап удаления

    componentWillUnmount(), в порядкеРодительский компонент выполняется первым, а дочерние компоненты выполняют свои соответствующие методы в порядке, определенном в JSX..

    Уведомление: Если удаление старого компонента сопровождается созданием нового компонента, то новый компонент будет создан и выполнен первым.render, затем выгружайте старые компоненты, которые не нужны, и, наконец, новый компонент выполняет обратный вызов завершения монтирования.

2. Компоненты Brother

  • горная сцена

    В случае синхронных маршрутов порядок, в котором они создаются, и порядок, в котором они определяются в общем родительском компоненте,последовательныйиз.

    В случае асинхронных маршрутов порядок их создания совпадает с порядком загрузки js.

  • этап обновления, этап удаления

    Связь между одноуровневыми узлами осуществляется в основном через родительский компонент (Redux и Context также передаются вниз путем изменения родительского компонента).propsосуществленный),Дизайн, удовлетворяющий требованиям React, следует модели одностороннего потока данных.,Таким образом, связь между любыми двумя компонентами можно по существу отнести к обновлению родительского и дочернего компонентов..

    Поэтому для получения сведений об обновлении и удалении компонентов Brother см.компонент родитель-потомок.

В заключение: я рекомендую онлайн-инструмент для редактирования:StackBlitz, может редактировать проекты Angular, React, TypeScript, RxJS, Ionic, Svelte онлайн

Примечание: Жизненный цикл высокоуровневых компонентов будет добавлен в будущем, так что следите за обновлениями маленькой бутылочки.

серия статей

Хотите увидеть больше статей в серии,Нажмите, чтобы перейти на домашнюю страницу блога github.

Прогулка в конце, добро пожаловать, чтобы обратить внимание: джентльмен переднего конца бутылки, ежедневное обновление

前端瓶子君