温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

React并发特性实例分析

发布时间:2022-08-23 16:21:04 来源:亿速云 阅读:150 作者:iii 栏目:开发技术

这篇文章主要介绍“React并发特性实例分析”,在日常操作中,相信很多人在React并发特性实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”React并发特性实例分析”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

    1. 如何表达渲染结果?

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

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

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

    	it('should render stateless component', () => {
    		const el = document.createElement('div');
    		ReactDOM.render(<FunctionComponent name="A" />, el);
    		expect(el.textContent).toBe('A');
    	});

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

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

    2. 如何测试并发环境?

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

    // 之前
    ReactDOM.render(<FunctionComponent name="A" />, el);
    expect(el.textContent).toBe('A');
    // 之后
    ReactDOM.createRoot(el).render(<FunctionComponent name="A" />);
    expect(el.textContent).toBe('A');

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

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

    ReactDOM.createRoot(el).render(<FunctionComponent name="A" />);
    setTimeout(() => {
      // 异步获取结果
      expect(el.textContent).toBe('A');
    })

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

    React的应对策略

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

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

    既然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不外乎setTimeoutsetIntervalPromise等。

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

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

    // 测试用例修改后:
    await act(() => {
      ReactDOM.createRoot(el).render(<FunctionComponent name="A" />);
    })
    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 <Child />;
    }
    function Child() {
      useEffect(() => {
        return () => Scheduler.unstable_yieldValue('Unmount child');
      });
      return 'Child';
    }
    await act(async () => {
      root.render(<Parent />);
    });

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

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

    到此,关于“React并发特性实例分析”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI