React:访问React内部操作队列

Eli*_*ski 11 reactjs

React收集操作(例如“ADD”、“REPLACE”、“REMOVE”等 DOM 操作),以便它们可以在每次渲染结束时一次性批量执行它们。

例如,React 组件内的 setState 调用通过将此操作添加到操作队列来安排到此 React 树的构造结束。然后 React 将遍历这个队列并决​​定需要对 DOM 进行哪些更改。

React 会根据操作队列是否为空来决定是否调用另一个渲染。

有关这个很棒的教程的更多信息,它总结了 React 内部工作原理的基础知识。

我需要访问此队列来确定当前渲染是否是非常自定义的 React 组件的最后一个渲染。(是的,也许我可以避免它,但目前,这是我的要求)
访问必须来自该组件的内部。

我的检查将从最新的 useEffect 调用,这是在渲染结束并且 DOM 更新之后,并且是最新的生命周期事件,因此如果操作队列为空,则肯定不会再进行渲染。(这里很好的文章解释并演示了钩子调用的顺序)

找不到任何公共 API,但解决方法也是可以接受的。(分叉和编辑 React 不是解决方法)

这个src 文件可能是这个队列的主要逻辑。这实际队列的类型。然而,这是源代码,并且该队列不会在 React 的构建版本中导出(无论是开发还是生产构建)

那么,有没有办法访问React的内部操作队列呢?

Eli*_*ski 14

编辑 - 警告

这仅用于教育目的 - 不要在生产中使用它! 根据 React 核心团队成员的说法,这种方法并不安全- 我已经问过了。如果您打算使用 React 的固定版本而不稍后升级,那么它可能是安全的。

####### 编辑结束 #######

解决了!

因此,在深入研究 React 代码库许多小时后,我终于编写了一个钩子来告诉当前是否计划进行任何更新。

注意:仅适用于功能组件,并且此钩子尚未经过充分测试。

你可以通过未记录的属性看到 React 的一些内部状态__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED。这个属性ReactCurrentOwner基本上是对当前正在构造的组件的引用。

const currentOwner = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner?.current;
Run Code Online (Sandbox Code Playgroud)

currentOwner是当前正在构建的组件。该道具仅在渲染期间可用(因为在效果中,渲染后当前没有构建任何组件)。但是因为可以从效果的状态集触发另一个渲染,所以我们应该始终从最新的效果调用下检查

在其中,.current.memoizedProps您将找到在此组件上声明的所有钩子的链接列表。

每个钩子都保存一个queue用于保存计划更新的内容,并且在其中,有一个pendingprop 告诉当前是否计划为下一个渲染进行任何更新。

我们可以运行这个链表来查明是否有任何钩子安排了更新:

const currentOwner = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner?.current;
Run Code Online (Sandbox Code Playgroud)

因此,总结一下,我们可以构建一个自定义挂钩来检查当前渲染是否是最新的预定渲染:

const wouldUpdate = (currentOwner) => {
  let newObj = currentOwner?.memoizedState;
  // go over the linked list of hooks and find out if there is any pending update
  while (newObj && 'next' in newObj) {
    newObj = newObj['next'];
    if (newObj?.queue?.pending) return true;
  }
  return false;
};

export const useDoesUpdateIsScheduled = () => {
  // @ts-ignore
  // hold the current owner ref so we could call it from effects
  const currentOwner = useRef(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner?.current);
  return () => wouldUpdate(currentOwner.current);
};
Run Code Online (Sandbox Code Playgroud)

这么少的代码花了这么多时间...;<

用法:

const wouldUpdate = (currentOwner) => {
  let newObj = currentOwner?.memoizedState;
  // go over the linked list of hooks and find out if there is any pending update
  while (newObj && 'next' in newObj) {
    newObj = newObj['next'];
    if (newObj?.queue?.pending) return true;
  }
  return false;
};

Run Code Online (Sandbox Code Playgroud)

安装测试组件的屏幕截图:

在此输入图像描述

您可以看到,在最新的渲染中,我们的钩子告诉我们没有待处理的更新。

您还可以从函数体调用wouldUpdate,但要考虑到可以从效果中安排更新(意味着在渲染时调用它不会捕获这些更新)

流行的“why-did-you-render”也使用这个未记录的__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED属性来实现其目标。

就是这样(实际上它不值得花几个小时,而且它可能会在各种情况下崩溃,因为这不是公共 API)。