Когда компонент функции входит в стадию рендеринга, он будет обработан функцией renderWithHooks. В качестве функции рендеринг функционального компонента на самом деле является вызовом функции, и функциональный компонент будет вызывать функцию ловушек, предоставляемую React. Во время начального монтирования и обновления используемые хуки-функции различаются. Например, useEffect, вызываемый во время начального монтирования, и useEffect, вызываемый во время последующих обновлений, являются одним и тем же хуком, но поскольку они вызываются во время двух разных процессов рендеринга. это две разные вещи. Это отличие связано с тем, что функциональный компонент должен поддерживать связанный список хуков.Связанный список необходимо создать при первом монтировании, а связанный список необходимо обновлять при последующем обновлении.
Функции ловушек, принадлежащие двум процедурам, будут назначены соответствующим процедурам.ReactCurrentDispatcher
на текущем имуществе. Поэтому перед вызовом функционального компонента необходимо определить текущий ReactCurrentDispatcher в соответствии с текущим этапом, чтобы на правильном этапе можно было вызвать правильную функцию ловушки.
export function renderWithHooks<Props, SecondAwrg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
// 区分是挂载还是更新过程,获取不同的hooks函数集合
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 调用函数组件,
let children = Component(props, secondArg);
...
return children;
}
HooksDispatcherOnMount
а такжеHooksDispatcherOnUpdate
, функции их внутренних хуков реализованы по-разному, одно из отличий состоит в том, что обработка связанного списка хуков на разных этапах происходит по-разному.
const HooksDispatcherOnMount: Dispatcher = {
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
...
};
const HooksDispatcherOnUpdate: Dispatcher = {
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
...
};
Знать список крючков
Будь то начальное монтирование или обновление, каждый раз, когда вызывается функция ловушек, будет создан соответствующий ей объект ловушки. Ниже приведена структура объекта-ловушки.
{
baseQueue: null,
baseState: 'hook1',
memoizedState: null,
queue: null,
next: {
baseQueue: null,
baseState: null,
memoizedState: 'hook2',
next: null
queue: null
}
}
Сгенерированные объекты-ловушки упорядочиваются таким образом, чтобы сформировать связанный список и сохранить их в функциональном компоненте fiber.memoizedState. В этом процессе есть очень важный указатель:workInProgressHook, который может косвенно отражать, какая функция ловушки в данный момент вызывается в компоненте, путем записи сгенерированного (обновленного) объекта ловушки. Каждый раз, когда вызывается функция-ловушка, указатель этого указателя перемещается на объект-ловушку, сгенерированный функцией-ловушкой. Например:
const HooksExp = () => {
const [ stateHookA, setHookA ] = useState('A')
useEffect(() => { console.log('B') })
const [ stateHookC, setHookC ] = useState('C')
return <div>Hook Example</div>
}
В приведенном выше примере в компоненте HooksExp вызываются три функции ловушек, а именно useState, useEffect и useState. Тогда процесс построения списка хуков можно резюмировать следующим образом, сосредоточив внимание на изменении workInProgressHook.
Вызовите useState('A'):
fiber.memoizedState: hookA
^
workInProgressHook
Вызов useEffect:
fiber.memoizedState: hookA -> hookB
^
workInProgressHook
Вызовите useState('C'):
fiber.memoizedState: hookA -> hookB -> hookC
^
workInProgressHook
Каждый раз, когда функция ловушки выполняется, она создает соответствующий объект ловушки для следующего шага.Например, useReducer смонтирует очередь обновлений на объект ловушки, а useEffect смонтирует список эффектов на объект ловушки. Процесс создания объекта ловушки на самом деле является процессом построения списка ловушек и обновления указателя workInProgressHook.
крепление компонента
При первом монтировании на компоненте нет информации о крючках, поэтому этот процесс в основном заключается в создании списка крючков на волокне. Вызов монтированияmountWorkInProgressHook
, он создаст хуки и соединит их в связанный список, одновременно обновит workInProgressHook и, наконец, вернет только что созданный хук, который представляет собой связанный список хуков.
function mountWorkInProgressHook(): Hook {
// 创建hook对象
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// workInProgressHook为null说明此时还没有hooks链表,
// 将新hook对象作为第一个元素挂载到fiber.memoizedState,
// 并将workInProgressHook指向它。
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// workInProgressHook不为null说明已经有hooks链表,此时将
// 新的hook对象连接到链表后边,并将workInProgressHook指向它。
workInProgressHook = workInProgressHook.next = hook;
}
// 返回的workInProgressHook即为新创建的hook
return workInProgressHook;
}
в настоящее времяRenderingFiber является узлом workInProgress
Мы можем получить объект ловушки, вызвав функцию ловушки в компоненте, например useState:
const HooksDispatcherOnMount: Dispatcher = {
...
useState: mountState,
...
};
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
// 获取hook对象
const hook = mountWorkInProgressHook();
// 对hook对象的处理
...
return [hook.memoizedState, dispatch];
}
обновление компонента
В процессе обновления, поскольку существует текущее дерево, узел workInProgress также имеет соответствующий текущий узел. Тогда, естественно, будет два связанных списка хуков, которые существуют в свойствах memorizedState текущего узла и узла workInProgress соответственно. Ввиду этого построение списка хуков процесса обновления требует участия еще одного указателя: currentHook. Он служит объектом-ловушкой, соответствующим workInProgressHook компонента при последнем обновлении, и на его основе могут быть созданы новые объекты-ловушки. Кроме того, вы также можете получить некоторые данные последнего объекта ловушки, такие как сравнение до и после зависимостей useEffect, и предыдущие зависимости могут быть получены через него.
currentTree
current.memoizedState = hookA -> hookB -> hookC
^
currentHook
|
workInProgress Tree |
|
workInProgress.memoizedState = hookA -> hookB
^
workInProgressHook
Следовательно, в дополнение к обновлению указателя workInProgressHook, процесс построения списка ловушек процесса обновления также обновляет указатель currentHook и максимально повторно использует currentHook для создания новых объектов ловушек.
Эта процедура вызываетupdateWorkInProgressHook
функция:
function updateWorkInProgressHook(): Hook {
// 确定nextCurrentHook的指向
let nextCurrentHook: null | Hook;
if (currentHook === null) {
// currentHook在函数组件调用完成时会被设置为null,
// 这说明组件是刚刚开始重新渲染,刚刚开始调用第一个hook函数。
// hooks链表为空
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
// current节点存在,将nextCurrentHook指向current.memoizedState
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
// 这说明已经不是第一次调用hook函数了,
// hooks链表已经有数据,nextCurrentHook指向当前的下一个hook
nextCurrentHook = currentHook.next;
}
// 确定nextWorkInProgressHook的指向
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
// workInProgress.memoizedState在函数组件每次渲染时都会被设置成null,
// workInProgressHook在函数组件调用完成时会被设置为null,
// 所以当前的判断分支说明现在正调用第一个hook函数,hooks链表为空
// 将nextWorkInProgressHook指向workInProgress.memoizedState,为null
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
// 走到这个分支说明hooks链表已经有元素了,将nextWorkInProgressHook指向
// hooks链表的下一个元素
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
// 依据上面的推导,nextWorkInProgressHook不为空说明hooks链表不为空
// 更新workInProgressHook、nextWorkInProgressHook、currentHook
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// 走到这个分支说明hooks链表为空
// 刚刚调用第一个hook函数,基于currentHook新建一个hook对象,
invariant(
nextCurrentHook !== null,
'Rendered more hooks than during the previous render.',
);
currentHook = nextCurrentHook;
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
// 依据情况构建hooks链表,更新workInProgressHook指针
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
Суммировать
Из этой статьи мы узнали, что вызов функции ловушек в функциональном компоненте формирует связанный список ловушек, и этот связанный список будет смонтирован в свойстве memoizedState волокна, соответствующего функциональному компоненту. Это закладывает основу для нашего последующего объяснения функций хуков.