В продолжение предыдущей статьиФункциональный дизайн компонентов под благословением 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
- В соответствии с операторами useEffect, вызываемыми последовательно в теле функции функционального компонента, связанный список строится и монтируется на
FiberNode.updateQueue
, структура данных узла связанного списка:
const effect: Effect = {
tag, // 用来标识依赖项有没有变动
create, // 用户使用useEffect传入的函数体
destroy, // 上述函数体执行后生成的用来清除副作用的函数
deps, // 依赖项列表
next: (null: any),
};
- После того, как компонент завершает визуализацию, он проходит по связанному списку для выполнения.
этап обновления: updateEffect
- Точно так же при повторном вызове оператора useEffect оценивается, что входящий список зависимостей отличается от узла связанного списка.
Effect.deps
Является ли он непротиворечивым (одинаково ли значение базового типа данных; одинакова ли ссылка на объект), если он непротиворечив, то вEffect.tag
отметить наNoHookEffect
.
этап выполнения
После того, как рендеринг каждого компонента будет завершен, он войдет в фазу выполнения useEffect:function commitHookEffectList()
:
- просмотреть связанный список
- Если вы столкнетесь
Effect.tag
отмечен наNoHookEffect
узел пропускается. - если
Effect.destroy
является типом функции, вам нужно выполнить функцию, которая очищает побочные эффекты (как для этогоEffect.destroy
Откуда он взялся, скоро расскажу) - воплощать в жизнь
Effect.create
, и сохраните результат выполнения вEffect.destroy
(Если разработчик не настроилreturn
, то результат естественноundefined
, то есть разработчик считает, что для текущего сегмента кода useEffect нет побочных эффектов, которые необходимо убрать); обратите внимание, что из-за замыканий,Effect.destroy
на самом деле иметь доступ к этомуEffect.create
Переменные в области видимости функции.
Обратите внимание, что:Это первый раунд побочных эффектов, затем выполните эффект колеса..
Другие API-интерфейсы React Hooks
Другие React Hooks Api На самом деле это почти тот же принцип: используйте структуру данных связанного списка для поддержания глобального состояния, судите о зависимостях, чтобы решить, обновлять ли состояние и т. д., которые не будут повторяться здесь.
Суммировать
В этой статье используется относительно утонченный язык для объяснения принципа React Hooks. Цель состоит в том, чтобы дать читателям перцептивное понимание и облегчить интервью, но на самом деле React Hooks имеет много деталей реализации. Если вам интересно, пожалуйста, прочитайте исходный код, записьздесь.