反应钩如何确定它们的组件?

bal*_*ton 12 reactjs react-hooks

我注意到当我使用react钩子时,子组件的状态更改不会重新呈现没有状态更改的父组件.这个代码沙箱可以看到:https://codesandbox.io/s/kmx6nqr4o

由于缺乏将组件作为参数传递给钩子,或者作为绑定上下文,我错误地认为反应钩子/状态更改只是触发整个应用程序重新呈现,就像秘银的工作方式,以及React的设计原则所述:

React以递归方式遍历树,并在单个tick中调用整个更新树的渲染函数.

相反,看起来反应钩子知道它们与哪个组件相关联,因此,渲染引擎知道只更新那个单独的组件,并且从不调用render其他任何东西,反对React的设计原则文档上面所说的.

  1. 钩子和组件之间的关联如何完成?

  2. 这个关联如何使反应知道只调用render状态改变的组件,而不是那些没有的组件?(在代码沙箱中,尽管子状态发生了变化,但从render不调用父元素)

  3. 当您将useState和setState的用法抽象为自定义钩子函数时,此关联如何仍然有效?(正如代码沙箱与setInterval钩子一样)

似乎答案就在这条路径下的resolveDispatcher,ReactCurrentOwner,react-reconciler.

Rya*_*ell 19

首先,如果您正在寻找关于钩子如何工作以及它们如何知道它们所绑定的组件实例的概念性解释,请参阅以下内容:

这个问题的目的(如果我正确理解了问题的意图)是深入了解React如何知道哪个组件实例在状态通过useState钩子返回的setter进行更改时重新呈现的实际实现细节.因为这将深入研究React实现细节,所以随着React实现的不断发展,肯定会逐渐变得不那么准确.当引用React代码的部分时,我将删除那些我觉得混淆了回答这个问题的最相关方面的行.

了解其工作原理的第一步是在React中找到相关代码.我将集中讨论三个要点:

  • 执行组件实例的呈现逻辑的代码(即对于函数组件,执行组件函数的代码)
  • useState代码
  • 通过调用返回的setter触发的代码 useState

第1部分 React如何知道调用的组件实例useState

找到执行渲染逻辑的React代码的一种方法是从渲染函数中抛出错误.问题的CodeSandbox的以下修改提供了一种触发该错误的简单方法:

编辑React钩子父与子状态

这为我们提供了以下堆栈跟踪:

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)
Run Code Online (Sandbox Code Playgroud)

所以首先我要关注renderWithHooks.它驻留在ReactFiberHooks中.

这是最相关的代码:

    currentlyRenderingFiber = workInProgress;
    nextCurrentHook = current !== null ? current.memoizedState : null;
    ReactCurrentDispatcher.current =
      nextCurrentHook === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
    let children = Component(props, refOrContext);
    currentlyRenderingFiber = null;
Run Code Online (Sandbox Code Playgroud)

currentlyRenderingFiber表示正在呈现的组件实例.这就是React知道useState调用与哪个组件实例相关的方式.不管你怎么打电话深入定制挂钩useState,它仍将您的组件的渲染中出现(在此行中发生的事情:let children = Component(props, refOrContext);),等反应过来还是会知道它是绑在currentlyRenderingFiber之前的渲染设置.

设置后currentlyRenderingFiber,它还会设置当前的调度程序.请注意,对于组件(HooksDispatcherOnMount)的初始安装与组件()的重新呈现,调度程序是不同的HooksDispatcherOnUpdate.我们将在第2部分回到这个方面.

第2部分会发生什么useState

ReactHooks中,我们可以找到以下内容:

    export function useState<S>(initialState: (() => S) | S) {
      const dispatcher = resolveDispatcher();
      return dispatcher.useState(initialState);
    }
Run Code Online (Sandbox Code Playgroud)

这将使我们进入ReactFiberHooks中useState功能.对于组件的初始安装与更新(即重新渲染),这的映射方式不同.

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));
}
Run Code Online (Sandbox Code Playgroud)

mountState上面的代码中要注意的重要部分是dispatch变量.该变量是您所在州的设定者,并从mountState最后返回:return [hook.memoizedState, dispatch];.dispatch只是dispatchAction函数(也在ReactFiberHooks.js中)与一些参数绑定,包括currentlyRenderingFiberqueue.我们将在第3部分中看看它们是如何发挥作用的,但请注意queue.dispatch同样的dispatch功能.

useState委托updateReducer(也在ReactFiberHooks中)进行更新(重新渲染)案例.我故意遗漏updateReducer下面的许多细节,除了看它如何处理返回与初始调用相同的setter.

    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];
    }
Run Code Online (Sandbox Code Playgroud)

您可以在上面看到queue.dispatch用于在重新渲染时返回相同的setter.

第3部分当你调用返回的setter时会发生什么useState

以下是签名action:

function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A)
Run Code Online (Sandbox Code Playgroud)

你的新州价值将是fiber.该queue工作bind将自动由于传递mountState呼叫fiber.的currentlyRenderingFiber(早期为保存在同一个对象useState代表的组件实例)将在调用同一个组件实例指出useState,允许反应过来的时候你给它一个新的状态值排队特定组件的重新渲染.

用于了解React Fiber Reconciler的一些额外资源以及哪些纤维是:

  • 我在最后添加了一些额外的资源来解释光纤架构,并试图稍微澄清我的陈述。 (2认同)