自定义hooks怎么触发上层组件更新的?

1,091 阅读3分钟

前言

因为经常会用自定义hooks,也有很多前端同学对此有一些用法上的心得文章,我觉得都挺好的。

于是我相由心生,思考了这么一个问题:自定义hooks怎么触发上层组件更新的?

思考

换而言之,自定义hook函数是怎么找到上层组件,这层关联关系是怎样的?

方向

我最开始是有两个解决问题的方向:

  1. 去各大搜索引擎网站里去搜索,可惜很多文章都是以偏概全的去分析hook实现原理,体现不出细节,其实知识点应该是一点点积累,源码那么多,断掌取义出来的,只是一个个没有联系的节点,很难去把它们之间的关系说清楚。
  2. 看源码,自己调试。

选择

毫无疑问选择2

思路

注:为了更加具体,这里分析组件第一次挂载hook时的经过。

  1. 找到hook与fiber关系建立的地方

先找到HooksDispatcherOnMountInDEV这个dispatcher,初始化调用useState的时候会执行这个方法,可以看到执行了mountState这个方法。

HooksDispatcherOnMountInDEV = {
  // ...
  useState: function (initialState) {
    console.log('%c⧭', 'color: #733d00', initialState);
    currentHookNameInDev = 'useState';
    mountHookTypesDev();
    var prevDispatcher = ReactCurrentDispatcher$1.current;
    ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;

    try {
      return mountState(initialState);
    } finally {
      ReactCurrentDispatcher$1.current = prevDispatcher;
    }
  },
}

接着看mountState这个方法,它返回了dispatch方法,即是修改hook state并触发组件更新的方法,这个地方dispatchAction.bind指向了当前fiber,这个地方便是建立hook state和对应上层组件的关键点!

function mountState(initialState) {
  var hook = mountWorkInProgressHook();

  if (typeof initialState === 'function') {
    // $FlowFixMe: Flow doesn't like mixed types
    initialState = initialState();
  }

  hook.memoizedState = hook.baseState = initialState;
  var queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState,
  });
  console.log('%c⧭', 'color: #917399', currentlyRenderingFiber$1);
  var dispatch = (queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue));
  return [hook.memoizedState, dispatch];
}

所以问题来了,currentlyRenderingFiber$1是怎么来的呢?可以找到是在renderWithHooks这个方法里,将workInProgress赋值于它的。

function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber$1 = workInProgress;
  console.log('%c renderWithHooks workInProgress ⧭', 'color: #d90000', workInProgress);

  {
    hookTypesDev = current !== null ? current._debugHookTypes : null;
    hookTypesUpdateIndexDev = -1; // Used for hot reloading:

    ignorePreviousDependencies = current !== null && current.type !== workInProgress.type;
  }
  // ...
}

workInProgress是怎么创建的呢,向上回溯,可以找到createWorkInProgress这个方法,所以我们来分析下这个方法:

workInProgress由createFiber方法创建,并修改了它的属性值。(createFiber方法的用途:可以创建不同类型的fiber)

我理解workInProgress它是一个个处理过程中的fiber。

function createWorkInProgress(current, pendingProps) {
  var workInProgress = current.alternate;

  if (workInProgress === null) {
    // We use a double buffering pooling technique because we know that we'll
    // only ever need at most two versions of a tree. We pool the "other" unused
    // node that we're free to reuse. This is lazily created to avoid allocating
    // extra objects for things that are never updated. It also allow us to
    // reclaim the extra memory if needed.
    workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;

    {
      // DEV-only fields
      workInProgress._debugID = current._debugID;
      workInProgress._debugSource = current._debugSource;
      workInProgress._debugOwner = current._debugOwner;
      workInProgress._debugHookTypes = current._debugHookTypes;
    }

    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type.

    workInProgress.type = current.type; // We already have an alternate.
    // Reset the effect tag.

    workInProgress.flags = NoFlags; // The effect list is no longer valid.

    workInProgress.nextEffect = null;
    workInProgress.firstEffect = null;
    workInProgress.lastEffect = null;

    {
      // We intentionally reset, rather than copy, actualDuration & actualStartTime.
      // This prevents time from endlessly accumulating in new commits.
      // This has the downside of resetting values for different priority renders,
      // But works for yielding (the common case) and should support resuming.
      workInProgress.actualDuration = 0;
      workInProgress.actualStartTime = -1;
    }
  }

  workInProgress.childLanes = current.childLanes;
  workInProgress.lanes = current.lanes;
  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so
  // it cannot be shared with the current fiber.

  var currentDependencies = current.dependencies;
  workInProgress.dependencies =
    currentDependencies === null
      ? null
      : {
          lanes: currentDependencies.lanes,
          firstContext: currentDependencies.firstContext,
        }; // These will be overridden during the parent's reconciliation

  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;

  {
    workInProgress.selfBaseDuration = current.selfBaseDuration;
    workInProgress.treeBaseDuration = current.treeBaseDuration;
  }

  {
    workInProgress._debugNeedsRemount = current._debugNeedsRemount;

    switch (workInProgress.tag) {
      case IndeterminateComponent:
      case FunctionComponent:
      case SimpleMemoComponent:
        workInProgress.type = resolveFunctionForHotReloading(current.type);
        break;

      case ClassComponent:
        workInProgress.type = resolveClassForHotReloading(current.type);
        break;

      case ForwardRef:
        workInProgress.type = resolveForwardRefForHotReloading(current.type);
        break;
    }
  }

  return workInProgress;
}

再往上,可以找到是在prepareFreshStack这个方法里执行createWorkInProgress的,到此我们可以知道,babel转换的fiber数据结构里,从root节点,向下遍历执行。

function prepareFreshStack(root, lanes) {
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
  var timeoutHandle = root.timeoutHandle;

  if (timeoutHandle !== noTimeout) {
    // The root previous suspended and scheduled a timeout to commit a fallback
    // state. Now that we have additional work, cancel the timeout.
    root.timeoutHandle = noTimeout; // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above

    cancelTimeout(timeoutHandle);
  }

  if (workInProgress !== null) {
    var interruptedWork = workInProgress.return;

    while (interruptedWork !== null) {
      unwindInterruptedWork(interruptedWork);
      interruptedWork = interruptedWork.return;
    }
  }

  workInProgressRoot = root;
  workInProgress = createWorkInProgress(root.current, null);
  workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
  workInProgressRootExitStatus = RootIncomplete;
  workInProgressRootFatalError = null;
  workInProgressRootSkippedLanes = NoLanes;
  workInProgressRootUpdatedLanes = NoLanes;
  workInProgressRootPingedLanes = NoLanes;

  {
    spawnedWorkDuringRender = null;
  }

  {
    ReactStrictModeWarnings.discardPendingWarnings();
  }
}

结语

遇到问题,我推荐还是先通过搜索引擎找相关问题是否有人已经做过分析,如果找到3~4篇还是没有解决你的问题的话,不建议再浪费时间,直接找源码debug来看。

如果觉得我写的对你有帮助的话,还请点个赞!!

谢谢!!