溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

React并發(fā)特性實(shí)例分析

發(fā)布時(shí)間:2022-08-23 16:21:04 來源:億速云 閱讀:151 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“React并發(fā)特性實(shí)例分析”,在日常操作中,相信很多人在React并發(fā)特性實(shí)例分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對(duì)大家解答”React并發(fā)特性實(shí)例分析”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

    1. 如何表達(dá)渲染結(jié)果?

    React可以對(duì)接不同宿主環(huán)境的渲染器,大家最熟悉的渲染器想必是ReactDOM,用于對(duì)接瀏覽器與Node環(huán)境(SSR)。

    對(duì)于一些場景,可以用ReactDOM的輸出結(jié)果做測試。

    比如,下面是使用ReactDOM的輸出結(jié)果測試無狀態(tài)組件的渲染結(jié)果是否符合預(yù)期(測試框架是jest):

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

    這里有個(gè)不方便的地方 &mdash;&mdash; 這個(gè)用例依賴瀏覽器環(huán)境與DOM API(比如用到document.createElement)。

    對(duì)于測試React內(nèi)部運(yùn)行機(jī)制這樣的場景,摻雜了宿主環(huán)境相關(guān)信息顯然會(huì)讓測試用例編寫起來更繁瑣。

    2. 如何測試并發(fā)環(huán)境?

    如果將上文的用例中ReactDOM.render改為ReactDOM.createRoot,那么用例就會(huì)失?。?/p>

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

    這是因?yàn)樵谛碌募軜?gòu)下,很多同步更新變成了并發(fā)更新,當(dāng)render執(zhí)行后,頁面還沒完成渲染。

    要讓上述用例成功,最簡單的修改方式是:

    ReactDOM.createRoot(el).render(<FunctionComponent name="A" />);
    setTimeout(() => {
      // 異步獲取結(jié)果
      expect(el.textContent).toBe('A');
    })

    如何優(yōu)雅的應(yīng)對(duì)這種變化?

    React的應(yīng)對(duì)策略

    接下來我們來看React團(tuán)隊(duì)的應(yīng)對(duì)方式。

    首先來看第一個(gè)問題 &mdash;&mdash; 如何表達(dá)渲染結(jié)果?

    既然ReactDOM渲染器對(duì)應(yīng)瀏覽器、Node環(huán)境,ReactNative渲染器對(duì)應(yīng)Native環(huán)境。

    那能不能為測試內(nèi)部運(yùn)行流程專門開發(fā)一個(gè)渲染器呢?

    答案是肯定的。

    這個(gè)渲染器叫React-Noop-Renderer。

    簡單的說,這個(gè)渲染器會(huì)渲染出純JS對(duì)象。

    實(shí)現(xiàn)一個(gè)渲染器

    React內(nèi)部有個(gè)叫Reconciler的包,他會(huì)引用一些操作宿主環(huán)境的API。

    比如如下方法用于向容器中插入節(jié)點(diǎn):

    function appendChildToContainer(child, container) {
    	// 具體實(shí)現(xiàn)
    }

    對(duì)于瀏覽器環(huán)境(ReactDOM),使用appendChild方法實(shí)現(xiàn)即可:

    function appendChildToContainer(child, container) {
    	// 使用appendChild方法
      container.appendChild(child);
    }

    打包工具(rollup)將Reconciler包與上述這類針對(duì)瀏覽器環(huán)境的API打包起來,就是ReactDOM包。

    React-Noop-Renderer中,與ReactDOM中的DOM節(jié)點(diǎn)對(duì)標(biāo)的是如下數(shù)據(jù)結(jié)構(gòu):

    const instance = {
      id: instanceCounter++,
      type: type,
      children: [],
      parent: -1,
      props
    };

    注意其中的children字段,用于保存子節(jié)點(diǎn)。

    所以appendChildToContainer方法在React-Noop-Renderer中可以實(shí)現(xiàn)的很簡單:

    function appendChildToContainer(child, container) {
    	const index = container.children.indexOf(child);
    	if (index !== -1) {
    		container.children.splice(index, 1);
    	}
    	container.children.push(child);
    };

    打包工具將Reconciler包與上述這類針對(duì)React-Noop的API打包起來,就是React-Noop-Renderer包。

    基于React-Noop-Renderer,可以完全脫離正常的宿主環(huán)境,測試Reconciler內(nèi)部的邏輯。

    接下來來看第二個(gè)問題。

    如何測試并發(fā)環(huán)境?

    并發(fā)特性再復(fù)雜,說到底也只是各種異步執(zhí)行代碼的策略,最終執(zhí)行策略的API不外乎setTimeout、setInterval、Promise等。

    jest中,可以模擬這些異步API,控制他們的執(zhí)行時(shí)機(jī)。

    比如上面的異步代碼,在React中的測試用例會(huì)這么寫:

    // 測試用例修改后:
    await act(() => {
      ReactDOM.createRoot(el).render(<FunctionComponent name="A" />);
    })
    expect(el.textContent).toBe('A');

    act方法來自jest-react包,他的內(nèi)部會(huì)執(zhí)行jest.runOnlyPendingTimers方法,讓所有等待中的計(jì)時(shí)器觸發(fā)回調(diào)。

    比如如下代碼:

    setTimeout(() => {
      console.log('執(zhí)行')
    }, 9999999)

    執(zhí)行jest.runOnlyPendingTimers后會(huì)立刻打印執(zhí)行。

    通過這種方式,人為控制React并發(fā)更新的速度,同時(shí)對(duì)框架代碼0侵入。

    除此之外,用于驅(qū)動(dòng)并發(fā)更新的Scheduler(調(diào)度器)模塊,本身也有一個(gè)針對(duì)測試的版本。

    在這個(gè)版本中,開發(fā)者可以手動(dòng)控制Scheduler的輸入、輸出。

    比如,我想測試組件卸載時(shí)useEffect回調(diào)的執(zhí)行順序。

    如下面代碼所示,其中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 />);
    });

    根據(jù)yieldValue的插入順序是否符合預(yù)期,就能確定useEffect的邏輯是否符合預(yù)期:

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

    到此,關(guān)于“React并發(fā)特性實(shí)例分析”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI