您好,登錄后才能下訂單哦!
這篇文章主要介紹“React并發(fā)特性實(shí)例分析”,在日常操作中,相信很多人在React并發(fā)特性實(shí)例分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對(duì)大家解答”React并發(fā)特性實(shí)例分析”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
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è)不方便的地方 —— 這個(gè)用例依賴瀏覽器環(huán)境與DOM API
(比如用到document.createElement
)。
對(duì)于測試React內(nèi)部運(yùn)行機(jī)制這樣的場景,摻雜了宿主環(huán)境相關(guān)信息顯然會(huì)讓測試用例編寫起來更繁瑣。
如果將上文的用例中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
團(tuán)隊(duì)的應(yīng)對(duì)方式。
首先來看第一個(gè)問題 —— 如何表達(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ì)象。
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ā)特性再復(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í)用的文章!
免責(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)容。