什么?React19暂停发布了?

23,118 阅读3分钟

在 4月25日 React 发布了 19RC 版本(发布主版本前的最后一步),而根据 React 核心团队成员 Joe Savona 的描述,React 出现了问题因此停止了 19 的发布直到他们找到一个合理的修复方式:

image.png

那发生了什么呢?

在 React18 中,当 <Suspense> 包裹多个 children 时,当第一个组件抛出了 promise 时,React 会继续渲染这个组件的兄弟节点。而 React 团队认为这是非常影响效率的,因为当抛出了 promise 之后意味着一定会展示 fallback,此时渲染兄弟节点是浪费性能的,因此在 React19 中这个行为被改变了,会立即展示 fallback,等到 promise 被 resolve 之后才会继续渲染兄弟节点。

React19 的重大失误

让我们来看一个实际的例子吧。

比如我们现在在 <Suspense> 中包裹了多个 <Display>组件,每个组件会发起请求拿到对应 id 的数据:

<Suspense fallback={<div>loading</div>}>
  <Display id={1} />
  <Display id={2} />
  <Display id={3} />
  <Display id={4} />
  <Display id={5} />
  <Display id={6} />
</Suspense>

然后我们来看一下 React18/19 的行为有什么不同:

React19

点击查看 Demo:codesandbox.io/p/sandbox/r…

20240630161809_rec_.gif

对应 Waterfall:

image.png

React18

点击查看 Demo:codesandbox.io/p/sandbox/r…

20240630162003_rec_.gif

可以看到更快的展示了内容,对应 Waterfall:

image.png

我们可以看到在 React19 时 Waterfall 是顺序的,也就是说后续的请求会在上一次请求完成后被发出,而我们看 React18 的 Waterfall 可以发现请求是同时被发出的。

也就是说从 React18 升级到 React19 之后,这意味着这会对你的应用造成性能问题。

我们可以看一下相关的改动 PR:github.com/facebook/re…

核心改动在 packages/react-reconciler/src/ReactFiberWorkLoop.js 其它大部分都是测试文件。

在原先当抛出错误之后会被 try-catch 捕获,然后在 handleThrow 中会更新 workInProgressSuspendedReason 值:

image.png

之后会走到 unwindSuspendedUnitOfWork,这里其实就是正常的流程了,也就是以 DFS 的方式继续遍历兄弟节点,各个 Fiber Node 通过 child、return、sibling 来关联:

image.png

然后当回到 Suspense 节点,并且在这个 Fiber Node 上进行标记,代表说现在应该去渲染 Suspense 的fallback 了,这样在 React18 中抛出 promise 的兄弟节点也有机会得到渲染。而在上面的 PR 中我们可以看到当 React 捕获到错误后会从当前节点直接向上寻找 Suspense:

do {
  // ...
  const next = unwindWork(current, incompleteWork, renderLanes);
  
  if (next !== null) { // 找到 Suspense 终止
    workInProgress = next;
    return;
  }

  const returnFiber = incompleteWork.return; // 往上找父节点
  
  // ...
  
  incompleteWork = returnFiber;
} while (incompleteWork !== null);

也就是说取消了渲染兄弟节点的过程,而是直接向上找,直到找到 Suspense,并从 Suspense 开始渲染。

如何解决

image.png

社区的一个建议是是否渲染兄弟节点还是直接渲染 Suspense fallback 应该是一个可选择的事情,比如我们可以向 Suspense 加入 strategy={"parallel" | "sequential"} 选项来动态的控制这种行为。

另外,在 RC 版本发布前 React 要经历很多阶段 experimental -> cancary -> beta -> next -> rc -> latest,而这个问题在 rc 之前并没有被社区发现,这使得 React 团队不得不思考如何让更多开发者使用起来从而获得更多反馈,而不仅仅为社区的一些框架比如 nextjs(狗头)服务。