Как реагирующие крючки определяют компонент, для которого они предназначены? - PullRequest
0 голосов
/ 30 декабря 2018

Я заметил, что когда я использовал ловушки реакции, изменение состояния дочернего компонента не переопределяет родительский компонент, у которого не было изменения состояния.Это видно из этой изолированной программной среды кода: https://codesandbox.io/s/kmx6nqr4o

Из-за отсутствия передачи компонента в ловушку в качестве аргумента или в качестве связующего контекста, я ошибочно думал, что реакция перехватов / изменений состояния просто вызванаполное рендеринг приложения, например, как работает мифрил, и что Принципы проектирования React гласит:

React рекурсивно обходит дерево и вызывает функции рендеринга всего обновленного дерева за один тик,

Вместо этого кажется, что перехватчики реагирования знают, с каким компонентом они связаны, и, таким образом, механизм рендеринга знает, что нужно обновить только этот единственный компонент и никогда не вызывать render для чего-либо еще, в отличие от чего-либоВыше приведен документ «Принципы проектирования React».

  1. Как выполняется связь между крючком и компонентом?

  2. Как эта связь делает так, чтобы реагироватьзнает, вызывать render только на компоненты, состояние которых изменилось, а не те, без?(в изолированной программной среде кода, несмотря на изменение дочернего состояния, родительский элемент render никогда не вызывается)

  3. Как эта связь все еще работает, когда вы абстрагируете использование useState и setState в customфункции хука?(как в песочнице кода с setInterval хуком)

Кажется, что где-то лежат ответы с этим следом resolDispatcher , ReactCurrentOwner , реагируют-выверке .

1 Ответ

0 голосов
/ 30 декабря 2018

Прежде всего, если вы ищете концептуальное объяснение того, как работают хуки и как они знают, к какому экземпляру компонента они привязаны, то смотрите следующее:

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

Первый шаг в понимании того, как это работает, - найти соответствующий код в React.Я остановлюсь на трех основных моментах:

  • код, который выполняет логику рендеринга для экземпляра компонента (т.е. для компонента функции, код, который выполняет функцию компонента)
  • useState code
  • код, вызванный вызовом установщика, возвращенного useState

Part 1 Как React узнает экземпляр компонента, вызвавший useState?

Один из способов найти код React, который выполняет логику рендеринга, - вызвать ошибку из функции рендеринга.Следующая модификация CodeSandbox вопроса предоставляет простой способ вызвать эту ошибку:

Edit React hooks parent vs child state

Это дает нам следующую трассировку стека:

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 и того, что такое волокна:

...