Вопросы на фронтенд-интервью: Анализ принципа React Hooks

React.js

В продолжение предыдущей статьиФункциональный дизайн компонентов под благословением React Hooks

Зачем изучать принципы реактивных крюков

Во-первых, с точки зрения полезности: текущая фронтенд-инфраструктура разделена на три части: React, Vue, Angular, а с тех пор, как версия React v16.8.0 официально запустила концепцию React Hooks, тренд сместился с оригинальные компоненты класса в функциональные компоненты.Это На уровне шаблонов проектирования, ментальных моделей и самых последних инноваций, пока вы говорите о своей способности реагировать, вас обязательно спросят на собеседовании о принципе React Hooks .

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

useState / useReducer

И useState, и useReducer связаны с извлечением и обновлением значений состояния. По сути разницы нет. С точки зрения реализации можно сказать, что useState — это упрощенная версия useReducer, в которой используется тот же набор логики.

Как React Hooks сохраняет состояние

В официальной документации React упоминается, что место, где хуки React сохраняют состояние, на самом деле такое же, как и у компонента класса; просмотрев исходный код, я обнаружил, что это утверждение верно, но оно не является исчерпывающим:

  • Значения состояния обоих монтируются на объект экземпляра компонентаFiberNodeизmemoizedStateв свойствах.
  • Две структуры данных для сохранения значений состояния совершенно разные, компонент класса напрямую сохраняет определенный разработчиком объект, смонтированный в свойстве состояния, вmemoizedStateсвойства; в то время как React Hooks используют связанные списки для сохранения состояния,memoizedStateТо, что содержит свойство, на самом деле является указателем головы этого связанного списка.

Давайте посмотрим, как выглядят узлы этого связанного списка — объект Hook:

// react-reconciler/src/ReactFiberHooks.js
export type Hook = {
  memoizedState: any, // 最新的状态值
  baseState: any, // 初始状态值,如`useState(0)`,则初始值为0
  baseUpdate: Update<any, any> | null,
  queue: UpdateQueue<any, any> | null, // 临时保存对状态值的操作,更准确来说是一个链表数据结构中的一个指针
  next: Hook | null,  // 指向下一个链表节点
};

В официальной документации всегда подчеркивалось, что вызов React Hooks может быть размещен только на верхнем уровне функционального компонента/тела пользовательской функции Hooks, потому что мы можем ассоциироваться с фактической сохраненной структурой данных только через порядок вызовов Hooks:

PS: Хотя приведенное выше согласуется с примерами useState и useReducer, на самом деле все хуки React хранятся в этом связанном списке.

Как React Hooks обновляет состояние

Если вы знакомы с API useState, мы все знаем, как обновить состояние:

const [name, setName] = useState('')
setName('张三')

Итак, каков принцип работы функции, используемой для обновления состояния (далее — диспетчера), возвращаемого useState?

Когда мы каждый раз вызываем диспетчер, мы не модифицируем значение состояния сразу (да, обновление значения состояния происходит асинхронно), а создаем операцию модификации — в соответствующем Hook-объектеqueueДобавьте новый узел в связанный список монтирования атрибутов:

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

const [name, setName] = useState('')
setName(name => name + 'a')
setName(name => name + 'b')
setName(name => name + 'c')

// 下次执行时就可以得到 name 的最新状态值为'abc'啦

useEffect

Метод сохранения useEffect аналогичен useState/useReducer, и он тоже монтируется в виде связанного списка.FiberNode.updateQueueсередина.

Ниже мы опишем принцип выполнения useEffect в соответствии с двумя жизненными циклами компонентов монтирования и обновления:

этап монтирования: mountEffect

  1. В соответствии с операторами useEffect, вызываемыми последовательно в теле функции функционального компонента, связанный список строится и монтируется наFiberNode.updateQueue, структура данных узла связанного списка:
 const effect: Effect = {
    tag, // 用来标识依赖项有没有变动
    create, // 用户使用useEffect传入的函数体
    destroy, // 上述函数体执行后生成的用来清除副作用的函数
    deps, // 依赖项列表
    next: (null: any),
};
  1. После того, как компонент завершает визуализацию, он проходит по связанному списку для выполнения.

этап обновления: updateEffect

  1. Точно так же при повторном вызове оператора useEffect оценивается, что входящий список зависимостей отличается от узла связанного списка.Effect.depsЯвляется ли он непротиворечивым (одинаково ли значение базового типа данных; одинакова ли ссылка на объект), если он непротиворечив, то вEffect.tagотметить наNoHookEffect.

этап выполнения

После того, как рендеринг каждого компонента будет завершен, он войдет в фазу выполнения useEffect:function commitHookEffectList():

  1. просмотреть связанный список
  2. Если вы столкнетесьEffect.tagотмечен наNoHookEffectузел пропускается.
  3. еслиEffect.destroyявляется типом функции, вам нужно выполнить функцию, которая очищает побочные эффекты (как для этогоEffect.destroyОткуда он взялся, скоро расскажу)
  4. воплощать в жизньEffect.create, и сохраните результат выполнения вEffect.destroy(Если разработчик не настроилreturn, то результат естественноundefined, то есть разработчик считает, что для текущего сегмента кода useEffect нет побочных эффектов, которые необходимо убрать); обратите внимание, что из-за замыканий,Effect.destroyна самом деле иметь доступ к этомуEffect.createПеременные в области видимости функции.

Обратите внимание, что:Это первый раунд побочных эффектов, затем выполните эффект колеса..

Другие API-интерфейсы React Hooks

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

Суммировать

В этой статье используется относительно утонченный язык для объяснения принципа React Hooks. Цель состоит в том, чтобы дать читателям перцептивное понимание и облегчить интервью, но на самом деле React Hooks имеет много деталей реализации. Если вам интересно, пожалуйста, прочитайте исходный код, записьздесь.