Прежде всего, если вы ищете концептуальное объяснение того, как работают хуки и как они знают, к какому экземпляру компонента они привязаны, то смотрите следующее:
Цель этого вопроса (если я правильно понимаю цель вопроса) состоит в том, чтобы глубже вникнуть в реальные детали реализации того, как React знает, какой экземпляр компонента нужно повторно обработать.рендеринг при изменении состояния через установщик, возвращаемый хуком useState
.Так как это углубится в детали реализации React, она будет постепенно становиться менее точной по мере развития реализации React с течением времени.При цитировании частей кода React я удаляю строки, которые, по моему мнению, запутывают наиболее важные аспекты ответа на этот вопрос.
Первый шаг в понимании того, как это работает, - найти соответствующий код в React.Я остановлюсь на трех основных моментах:
- код, который выполняет логику рендеринга для экземпляра компонента (т.е. для компонента функции, код, который выполняет функцию компонента)
useState
code - код, вызванный вызовом установщика, возвращенного
useState
Part 1 Как React узнает экземпляр компонента, вызвавший useState
?
Один из способов найти код React, который выполняет логику рендеринга, - вызвать ошибку из функции рендеринга.Следующая модификация CodeSandbox вопроса предоставляет простой способ вызвать эту ошибку:
![Edit React hooks parent vs child state](https://codesandbox.io/static/img/play-codesandbox.svg)
Это дает нам следующую трассировку стека:
Uncaught Error: Error in child render
at Child (index.js? [sm]:24)
at renderWithHooks (react-dom.development.js:15108)
at updateFunctionComponent (react-dom.development.js:16925)
at beginWork$1 (react-dom.development.js:18498)
at HTMLUnknownElement.callCallback (react-dom.development.js:347)
at Object.invokeGuardedCallbackDev (react-dom.development.js:397)
at invokeGuardedCallback (react-dom.development.js:454)
at beginWork$$1 (react-dom.development.js:23217)
at performUnitOfWork (react-dom.development.js:22208)
at workLoopSync (react-dom.development.js:22185)
at renderRoot (react-dom.development.js:21878)
at runRootCallback (react-dom.development.js:21554)
at eval (react-dom.development.js:11353)
at unstable_runWithPriority (scheduler.development.js:643)
at runWithPriority$2 (react-dom.development.js:11305)
at flushSyncCallbackQueueImpl (react-dom.development.js:11349)
at flushSyncCallbackQueue (react-dom.development.js:11338)
at discreteUpdates$1 (react-dom.development.js:21677)
at discreteUpdates (react-dom.development.js:2359)
at dispatchDiscreteEvent (react-dom.development.js:5979)
Итак, сначала я сосредоточусь на renderWithHooks
.Это находится в ReactFiberHooks .Если вы хотите изучить больше пути к этой точке, ключевыми точками выше в трассировке стека являются функции beginWork и updateFunctionComponent , которые обе находятся в ReactFiberBeginWork.js.
Вот наиболее подходящий код:
currentlyRenderingFiber = workInProgress;
nextCurrentHook = current !== null ? current.memoizedState : null;
ReactCurrentDispatcher.current =
nextCurrentHook === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, refOrContext);
currentlyRenderingFiber = null;
currentlyRenderingFiber
представляет визуализируемый экземпляр компонента.Вот как React знает, к какому экземпляру компонента относится вызов useState
.Независимо от того, насколько глубоко в пользовательских хуках вы называете useState
, это все равно будет происходить при рендеринге вашего компонента (происходит в этой строке: let children = Component(props, refOrContext);
), поэтому React все равно будет знать, что он привязан к currentlyRenderingFiber
, установленному дорендеринг.
После установки currentlyRenderingFiber
также устанавливается текущий диспетчер.Обратите внимание, что диспетчер отличается для первоначального монтирования компонента (HooksDispatcherOnMount
) и повторного рендеринга компонента (HooksDispatcherOnUpdate
).Мы вернемся к этому аспекту в Части 2.
Часть 2 Что происходит в useState
?
В ReactHooks , которые мы можем найтиследующее:
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
Это приведет нас к функции useState
в ReactFiberHooks .Это сопоставляется по-разному для первоначального монтирования компонента в сравнении с обновлением (то есть повторным рендерингом).
const HooksDispatcherOnMount: Dispatcher = {
useReducer: mountReducer,
useState: mountState,
};
const HooksDispatcherOnUpdate: Dispatcher = {
useReducer: updateReducer,
useState: updateState,
};
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
last: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
// Flow doesn't know this is non-null, but we do.
((currentlyRenderingFiber: any): Fiber),
queue,
): any));
return [hook.memoizedState, dispatch];
}
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
Важная часть, которую следует заметить в приведенном выше коде mountState
, - это переменная dispatch
.Эта переменная является установщиком для вашего состояния и возвращается из mountState
в конце: return [hook.memoizedState, dispatch];
.dispatch
- это просто функция dispatchAction
(также в ReactFiberHooks.js), с которой связаны некоторые аргументы, включая currentlyRenderingFiber
и queue
.Мы рассмотрим, как они вступают в игру в части 3, но заметим, что queue.dispatch
указывает на эту же функцию dispatch
.
useState
делегирует updateReducer
(также в ReactFiberHooks) для случая обновления (повторного рендеринга).Я намеренно опускаю многие детали из updateReducer
ниже, за исключением того, чтобы увидеть, как он обрабатывает возврат того же установщика, что и первоначальный вызов.
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
Вы можете видеть выше, что queue.dispatch
используется длявернуть тот же установщик при повторном рендеринге.
Часть 3 Что происходит, когда вы вызываете установщик, возвращенный useState
?
Вот подпись для dispatchAction :
function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A)
Ваше новое значение состояния будет action
.fiber
и работа queue
будут переданы автоматически из-за вызова bind
в mountState
.fiber
(тот же объект, сохраненный ранее как currentlyRenderingFiber
, который представляет экземпляр компонента) будет указывать на тот же экземпляр компонента, который называется useState
, что позволяет React поставить в очередь повторную визуализацию этого конкретного компонента, когда вы дадите емуновое значение состояния.
Некоторые дополнительные ресурсы для понимания React Fiber Reconciler и того, что такое волокна: