当前位置: 当前位置:首页 >系统运维 >React团队是如何测试并发特性的正文

React团队是如何测试并发特性的

作者:人工智能 来源:人工智能 浏览: 【】 发布时间:2025-11-05 08:51:27 评论数:

大家好,团特性我卡颂。队何

React18进入大家视野已经有一段时间了,测试不知道各位有没有尝试「并发特性」呢?团特性

React团队是如何测试并发特性的

当启用「并发特性」后,React会从「同步更新」变为「异步、队何带优先级、测试可中断的团特性更新」。

这也为编写单元测试带来了一些难度。队何

本文来聊聊React团队如何测试并发特性。测试

遇到的团特性困境

主要有两个问题需要面对。

1. 如何表达渲染结果?队何

React可以对接不同宿主环境的渲染器,大家最熟悉的测试渲染器想必是ReactDOM,用于对接「浏览器」与「Node环境」(SSR)。团特性

对于一些场景,队何可以用ReactDOM的测试输出结果做测试。

比如,下面是使用ReactDOM的输出结果测试「无状态组件的渲染结果是否符合预期」(测试框架是jest):

it(should render stateless component, () => {

const el = document.createElement(div);

ReactDOM.render(, el);

expect(el.textContent).toBe(A);

});

这里有个不方便的地方 —— 这个用例依赖浏览器环境与DOM API(比如用到document.createElement)。

对于测试「React内部运行机制」这样的场景,掺杂了宿主环境相关信息显然会让测试用例编写起来更繁琐。

2. 如何测试并发环境?

如果将上文的用例中ReactDOM.render改为ReactDOM.createRoot,网站模板那么用例就会失败:

// 之前

ReactDOM.render(, el);

expect(el.textContent).toBe(A);

// 之后

ReactDOM.createRoot(el).render();

expect(el.textContent).toBe(A);

这是因为在新的架构下,很多「同步更新」变成了「并发更新」,当render执行后,页面还没完成渲染。

要让上述用例成功,最简单的修改方式是:

ReactDOM.createRoot(el).render();

setTimeout(() => {

// 异步获取结果

expect(el.textContent).toBe(A);

})

如何优雅的应对这种变化?

React的应对策略

接下来我们来看React团队的应对方式。

首先来看第一个问题 —— 如何表达渲染结果?

既然ReactDOM渲染器对应浏览器、Node环境,ReactNative渲染器对应Native环境。

那能不能为测试「内部运行流程」专门开发一个渲染器呢?

答案是肯定的。

这个渲染器叫React-Noop-Renderer。

简单的说,这个渲染器会渲染出纯JS对象。

实现一个渲染器

React内部有个叫Reconciler的包,他会引用一些「操作宿主环境」的API。

比如如下方法用于「向容器中插入节点」:

function appendChildToContainer(child, container) {

// 具体实现

}

对于浏览器环境(ReactDOM),使用appendChild方法实现即可:

function appendChildToContainer(child, container) {

// 使用appendChild方法

container.appendChild(child);

}

打包工具(rollup)将Reconciler包与上述这类「针对浏览器环境的API」打包起来,就是ReactDOM包。

在React-Noop-Renderer中,香港云服务器与ReactDOM中的DOM节点对标的是如下数据结构:

const instance = {

id: instanceCounter++,

type: type,

children: [],

parent: -1,

props

};

注意其中的children字段,用于保存子节点。

所以appendChildToContainer方法在React-Noop-Renderer中可以实现的很简单:

function appendChildToContainer(child, container) {

const index = container.children.indexOf(child);

if (index !== -1) {

container.children.splice(index, 1);

}

container.children.push(child);

};

打包工具将Reconciler包与上述这类「针对React-Noop的API」打包起来,就是React-Noop-Renderer包。

基于React-Noop-Renderer,可以完全脱离正常的宿主环境,测试Reconciler内部的逻辑。

接下来来看第二个问题。

如何测试并发环境?

「并发特性」再复杂,说到底也只是「各种异步执行代码的策略」,最终执行策略的API不外乎setTimeout、setInterval、Promise等。

在jest中,可以模拟这些异步API,控制他们的执行时机。

比如上面的异步代码,在React中的测试用例会这么写:

// 测试用例修改后:

await act(() => {

ReactDOM.createRoot(el).render();

})

expect(el.textContent).toBe(A);

act方法来自jest-react包,站群服务器他的内部会执行jest.runOnlyPendingTimers方法,让所有等待中的计时器触发回调。

比如如下代码:

setTimeout(() => {

console.log(执行)

}, 9999999)

执行jest.runOnlyPendingTimers后会立刻打印「执行」。

通过这种方式,人为控制React并发更新的速度,同时对框架代码0侵入。

除此之外,用于驱动并发更新的Scheduler(调度器)模块,本身也有一个针对测试的版本。

在这个版本中,开发者可以手动控制Scheduler的输入、输出。

比如,我想测试组件卸载时useEffect回调的执行顺序。

如下面代码所示,其中Parent为挂载的「被测试组件」:

function Parent() {

useEffect(() => {

return () => Scheduler.unstable_yieldValue(Unmount parent);

});

return ;

}

function Child() {

useEffect(() => {

return () => Scheduler.unstable_yieldValue(Unmount child);

});

return Child;

}

await act(async () => {

root.render();

});

根据yieldValue的插入顺序是否符合预期,就能确定useEffect的逻辑是否符合预期:

expect(Scheduler).toHaveYielded([Unmount parent, Unmount child]);

总结

React中测试用例的编写策略为:

可以用ReactDOM测的用例,一般结合ReactDOM与ReactTestUtils(浏览器环境的辅助方法)完成

需要把控中间过程的用例,使用Scheduler的测试包,用Scheduler.unstable_yieldValue记录过程信息

脱离宿主环境,单独测试React内部运行流程的,使用React-Noop-Renderer

测试并发下的场景,需要结合上述工具与jest-react一起使用

如果想深入学习下React中与测试相关的技巧,可以看下司徒正美老师的作品anu[1]。

这是个类React框架,但能跑通800+的React用例。里面实现了ReactTestUtils、React-Noop-Renderer的简化版。

最近更新