Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
從項目的整體架構(gòu)來看,要選擇適合項目背景的極速。如果項目背景不適合使用狀態(tài)管理器,那就沒有一定的必要性去使用,比如微信小程序等,可以從以下幾個維度來看
什么是渲染劫持,渲染劫持的概念是控制組件從另一個組件輸出的能力,當(dāng)然這個概念一般和react中的高階組件(HOC)放在一起解釋比較有明了。
高階組件可以在render函數(shù)中做非常多的操作,從而控制原組件的渲染輸出,只要改變了原組件的渲染,我們都將它稱之為一種渲染劫持。
實際上,在高階組件中,組合渲染和條件渲染都是渲染劫持的一種,通過反向繼承,不僅可以實現(xiàn)以上兩點,還可以增強(qiáng)由原組件 render 函數(shù)產(chǎn)生的 React元素。
實際的操作中通過操作 state、props 都可以實現(xiàn)渲染劫持
依賴于 i18next 的方案,對于龐大的業(yè)務(wù)項目有個很蛋疼的問題,那就是 json 文件的維護(hù)。每次產(chǎn)品迭代都需要增加新的配置,那么這份配置由誰來維護(hù),怎么維護(hù),都會有很多問題,而且如果你的項目要支持幾十個國家的語言,那么這幾十份文件又怎么維護(hù)。
所以現(xiàn)在大廠比較常用的方案是,使用 AST,每次開發(fā)完新版本,通過 AST 去掃描所有的代碼,找出代碼中的中文,以中文為 key,調(diào)用智能翻譯服務(wù),去幫項目自動生成 json 文件。這樣,再也不需要人為去維護(hù) json 文件,一切都依賴工具進(jìn)行自動化。目前已經(jīng)有大廠開源,比如滴滴的 di18n,阿里的 kiwi
我認(rèn)為 react 的拆分前提是代碼目錄設(shè)計規(guī)范,模塊定義規(guī)范,代碼設(shè)計規(guī)范,符合程序設(shè)計的一般原則,例如高內(nèi)聚、低耦合等等。
在我們的react項目中:
官網(wǎng)例子:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state={ hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能夠顯示降級后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同樣可以將錯誤日志上報給服務(wù)器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定義降級后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
使用
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
但是錯誤邊界不會捕獲:
try{}catch(err){}
///異步代碼(例如 setTimeout 或 requestAnimationFrame 回調(diào)函數(shù))
///服務(wù)端渲染
///它自身拋出來的錯誤(并非它的子組件)
保證react的單向數(shù)據(jù)流的設(shè)計模式,使?fàn)顟B(tài)更可預(yù)測。如果允許自組件修改,那么一個父組件將狀態(tài)傳遞給好幾個子組件,這幾個子組件隨意修改,就完全不可預(yù)測,不知道在什么地方修改了狀態(tài),所以我們必須像純函數(shù)一樣保護(hù) props 不被修改
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData]=useState({ hits: [] });
useEffect(async ()=> {
const result=await axios(
'https://api/url/to/data',
);
setData(result.data);
});
return (
<ul>
{data.hits.map(item=> (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook, 確保總是在你的 React 函數(shù)的最頂層調(diào)用他們。
不要在普通的 JavaScript 函數(shù)中調(diào)用 Hook。你可以:
React15 的 StackReconciler 方案由于遞歸不可中斷問題,如果 Diff 時間過長(JS計算時間),會造成頁面 UI 的無響應(yīng)(比如輸入框)的表現(xiàn),vdom 無法應(yīng)用到 dom 中。
為了解決這個問題,React16 實現(xiàn)了新的基于 requestIdleCallback 的調(diào)度器(因為 requestIdleCallback 兼容性和穩(wěn)定性問題,自己實現(xiàn)了 polyfill),通過任務(wù)優(yōu)先級的思想,在高優(yōu)先級任務(wù)進(jìn)入的時候,中斷 reconciler。
為了適配這種新的調(diào)度器,推出了 FiberReconciler,將原來的樹形結(jié)構(gòu)(vdom)轉(zhuǎn)換成 Fiber 鏈表的形式(child/sibling/return),整個 Fiber 的遍歷是基于循環(huán)而非遞歸,可以隨時中斷。
更加核心的是,基于 Fiber 的鏈表結(jié)構(gòu),對于后續(xù)(React 17 lane 架構(gòu))的異步渲染和 (可能存在的)worker 計算都有非常好的應(yīng)用基礎(chǔ)
參考官網(wǎng)
官網(wǎng)回答:
Hook 解決了我們五年來編寫和維護(hù)成千上萬的組件時遇到的各種各樣看起來不相關(guān)的問題。無論你正在學(xué)習(xí) React,或每天使用,或者更愿嘗試另一個和 React 有相似組件模型的框架,你都可能對這些問題似曾相識。
React 沒有提供將可復(fù)用性行為“附加”到組件的途徑(例如,把組件連接到 store)。如果你使用過 React 一段時間,你也許會熟悉一些解決此類問題的方案,比如 render props 和 高階組件。但是這類方案需要重新組織你的組件結(jié)構(gòu),這可能會很麻煩,使你的代碼難以理解。如果你在 React DevTools 中觀察過 React 應(yīng)用,你會發(fā)現(xiàn)由 providers,consumers,高階組件,render props 等其他抽象層組成的組件會形成“嵌套地獄”。盡管我們可以在 DevTools 過濾掉它們,但這說明了一個更深層次的問題:React 需要為共享狀態(tài)邏輯提供更好的原生途徑。
你可以使用 Hook 從組件中提取狀態(tài)邏輯,使得這些邏輯可以單獨測試并復(fù)用。Hook 使你在無需修改組件結(jié)構(gòu)的情況下復(fù)用狀態(tài)邏輯。 這使得在組件間或社區(qū)內(nèi)共享 Hook 變得更便捷。
我們經(jīng)常維護(hù)一些組件,組件起初很簡單,但是逐漸會被狀態(tài)邏輯和副作用充斥。每個生命周期常常包含一些不相關(guān)的邏輯。例如,組件常常在 componentDidMount 和 componentDidUpdate 中獲取數(shù)據(jù)。但是,同一個 componentDidMount 中可能也包含很多其它的邏輯,如設(shè)置事件監(jiān)聽,而之后需在 componentWillUnmount 中清除。相互關(guān)聯(lián)且需要對照修改的代碼被進(jìn)行了拆分,而完全不相關(guān)的代碼卻在同一個方法中組合在一起。如此很容易產(chǎn)生 bug,并且導(dǎo)致邏輯不一致。
在多數(shù)情況下,不可能將組件拆分為更小的粒度,因為狀態(tài)邏輯無處不在。這也給測試帶來了一定挑戰(zhàn)。同時,這也是很多人將 React 與狀態(tài)管理庫結(jié)合使用的原因之一。但是,這往往會引入了很多抽象概念,需要你在不同的文件之間來回切換,使得復(fù)用變得更加困難。
為了解決這個問題,Hook 將組件中相互關(guān)聯(lián)的部分拆分成更小的函數(shù)(比如設(shè)置訂閱或請求數(shù)據(jù)),而并非強(qiáng)制按照生命周期劃分。你還可以使用 reducer 來管理組件的內(nèi)部狀態(tài),使其更加可預(yù)測。
除了代碼復(fù)用和代碼管理會遇到困難外,我們還發(fā)現(xiàn) class 是學(xué)習(xí) React 的一大屏障。你必須去理解 JavaScript 中 this 的工作方式,這與其他語言存在巨大差異。還不能忘記綁定事件處理器。沒有穩(wěn)定的語法提案,這些代碼非常冗余。大家可以很好地理解 props,state 和自頂向下的數(shù)據(jù)流,但對 class 卻一籌莫展。即便在有經(jīng)驗的 React 開發(fā)者之間,對于函數(shù)組件與 class 組件的差異也存在分歧,甚至還要區(qū)分兩種組件的使用場景。
另外,React 已經(jīng)發(fā)布五年了,我們希望它能在下一個五年也與時俱進(jìn)。就像 Svelte,Angular,Glimmer等其它的庫展示的那樣,組件預(yù)編譯會帶來巨大的潛力。尤其是在它不局限于模板的時候。最近,我們一直在使用 Prepack 來試驗 component folding,也取得了初步成效。但是我們發(fā)現(xiàn)使用 class 組件會無意中鼓勵開發(fā)者使用一些讓優(yōu)化措施無效的方案。class 也給目前的工具帶來了一些問題。例如,class 不能很好的壓縮,并且會使熱重載出現(xiàn)不穩(wěn)定的情況。因此,我們想提供一個使代碼更易于優(yōu)化的 API。
為了解決這些問題,Hook 使你在非 class 的情況下可以使用更多的 React 特性。 從概念上講,React 組件一直更像是函數(shù)。而 Hook 則擁抱了函數(shù),同時也沒有犧牲 React 的精神原則。Hook 提供了問題的解決方案,無需學(xué)習(xí)復(fù)雜的函數(shù)式或響應(yīng)式編程技術(shù)
React 官網(wǎng)是這么簡介的。JavaScript library for building user interfaces.專注 view 層 的特點決定了它不是一個全能框架,相比 angular 這種全能框架,React 功能較簡單,單一。比如說沒有前端路由,沒有狀態(tài)管理,沒有一站式開發(fā)文檔等。
react 組件是根據(jù) state (或者 props)去渲染頁面的,類似于一個函數(shù),輸入 state,輸出 view。不過這不是完整意義上的 MDV(Model Driven View),沒有完備的 model 層。順便提一句,感覺現(xiàn)在的組件化和 MDV 在前端開發(fā)中正火熱,大勢所趨...
從我們最開始寫 React 開始,就了解這條特點了。state 流向是自組件從外到內(nèi),從上到下的,而且傳遞下來的 props 是只讀的,如果你想更改 props,只能上層組件傳下一個包裝好的 setState 方法。不像 angular 有 ng-model, vue 有 v-model, 提供了雙向綁定的指令。React 中的約定就是這樣,你可能覺得這很繁瑣,不過 state 的流向卻更清晰了,單向數(shù)據(jù)流在大型 spa 總是要討好一些的。
這些特點決定了,React 本身是沒有提供強(qiáng)大的狀態(tài)管理功能的,原生大概是三種方式。
它沒有提供生命周期概念,不像 class 組件繼承 React.component,可以讓你使用生命周期以及特意強(qiáng)調(diào)相關(guān)概念
使用字典樹持久化數(shù)據(jù)結(jié)構(gòu),更新時可優(yōu)化對象生成邏輯,降低成本
dangerouslySetInnerHTML
react 基于虛擬 DOM 和高效 Diff算法的完美配合,實現(xiàn)了對 DOM最小粒度的更新,大多數(shù)情況下,React對 DOM的渲染效率足以我們的業(yè)務(wù)日常
復(fù)雜業(yè)務(wù)場景下,性能問題依然會困擾我們。此時需要采取一些措施來提升運(yùn)行性能,避免不必要的渲染則是業(yè)務(wù)中常見的優(yōu)化手段之一
在實際開發(fā)過程中,前端性能問題是一個必須考慮的問題,隨著業(yè)務(wù)的復(fù)雜,遇到性能問題的概率也在增高
除此之外,建議將頁面進(jìn)行更小的顆粒化,如果一個過大,當(dāng)狀態(tài)發(fā)生修改的時候,就會導(dǎo)致整個大組件的渲染,而對組件進(jìn)行拆分后,粒度變小了,也能夠減少子組件不必要的渲染
高階函數(shù)(Higher-order function),至少滿足下列一個條件的函數(shù)
在React中,高階組件即接受一個或多個組件作為參數(shù)并且返回一個組件,本質(zhì)也就是一個函數(shù),并不是一個組件
const EnhancedComponent=highOrderComponent(WrappedComponent);
上述代碼中,該函數(shù)接受一個組件 WrappedComponent 作為參數(shù),返回加工過的新組件 EnhancedComponent
高階組件的這種實現(xiàn)方式,本質(zhì)上是一個裝飾者設(shè)計模式
Refs 在計算機(jī)中稱為彈性文件系統(tǒng)(英語:Resilient File System,簡稱ReFS)
React 中的 Refs提供了一種方式,允許我們訪問 DOM節(jié)點或在 render方法中創(chuàng)建的 React元素
本質(zhì)為ReactDOM.render()返回的組件實例,如果是渲染組件則返回的是組件實例,如果渲染dom則返回的是具體的dom節(jié)點
class
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef=React.createRef();
}
render() {
return <div ref="myref" />;
}
}
hooks
function App(props) {
const myref=useRef()
return (
<>
<div ref={myref}></div>
</>
)
}
作者:野生程序猿江辰
鏈接:https://juejin.cn/post/7280439887962144820
開發(fā)者,在后端僅提供原始數(shù)據(jù)集的情況下,如何讓所有搜索邏輯都在前端完成?不僅如此,還要能夠?qū)崿F(xiàn)預(yù)測用戶輸入、忽略錯別字輸入、快速自動補(bǔ)齊等智能功能?本文的作者就深入 JavaScript 技術(shù)特性,談一談 React、性能優(yōu)化以及多線程那些事兒。
第一篇 問題闡述
我有一個看似很簡單的任務(wù):“有一個從后端檢索的數(shù)據(jù)集,其中包含13,000個數(shù)據(jù)項,每一項都是冗長羅嗦的名稱(科學(xué)組織)。使用這些數(shù)據(jù)創(chuàng)建一個帶自動補(bǔ)齊功能的搜索欄。”
你也不覺得很難,對不對?
難點1:
不能使用后端。后端只能提供原始的數(shù)據(jù)集,所有搜索邏輯都必須在前端完成。
難點2:
開發(fā)人員(我):“這些組織名稱這么長,需要花點心思。如果我們只是運(yùn)行簡單的字符串匹配,而且用戶可能會輸錯或出現(xiàn)拼寫錯誤,那么就很難搜索到任何結(jié)果。”
客戶:“你說的對,我們必須加入一些智能的功能,預(yù)測用戶的輸入,忽略輸入的錯別字。”
注意:一般情況下,我不建議你在未經(jīng)項目經(jīng)理同意下,提示客戶沒有提到的復(fù)雜功能!這種情況稱為特征蔓延(feature creep)。在上述例子中,只是恰巧我一個人負(fù)責(zé)這個合同,我有足夠的精力,所以我認(rèn)為這是一個有趣的挑戰(zhàn)。
難點3:
這是最大的難點。我選擇的智能(也稱為“模糊”)搜索引擎非常慢……
隨著搜索詞的長度加長,這個搜索算法庫的搜索時間會迅速增加。此外,看看下面這個龐大的列表,里面的數(shù)據(jù)項極其冗長,用戶需要輸入一個很長的搜索詞才能出現(xiàn)自動補(bǔ)齊提示。別無他法,搜索引擎跟不上用戶的打字速度,這個UI可能會廢掉。
我不知道是不是因為我選擇的這個搜索算法庫太過糟糕,我也不知道是不是因為所有“模糊”搜索算法都是這樣的情形。但幸運(yùn)的是,我沒有費心去尋找其他選擇。
盡管上述難點大多數(shù)是我自己強(qiáng)加上去的,但我依然決定堅持到底,努力優(yōu)化這種實現(xiàn)。雖然我知道這可能不是最佳策略,但是這個項目的情況允許我這么做,結(jié)果將說明一切,最重要的是,對我來說這是一次絕佳的學(xué)習(xí)體驗和成長的機(jī)會。
第二篇 問題剖析
在第一個實現(xiàn)中,我使用了UI的react-autocomplete和react-virtualized庫。
render () { return ( <Autocomplete value={this.state.searchTerm} items={this.getSearchResults()} renderMenu={this.reactVirtualizedList} renderItem={this.renderItem} getItemValue={ item=> item.name } onChange={(e, value)=> this.setState({searchTerm: value})} /> ) }
Autocomplete組件需要傳遞以下幾項:value屬性,需要傳入輸入框中的searchTeam;items屬性,傳入搜索結(jié)果;以及renderMenu函數(shù),該函數(shù)將搜索結(jié)果列表傳遞給react-vertualized。
react-virtualized能夠很好地處理大型列表(它只會渲染列表顯示在屏幕上的一小部分,只有滾動列表時才會更新)。考慮到我們需要渲染的組件并不多,我認(rèn)為應(yīng)該不會有太嚴(yán)重的性能問題。
更新操作的聲明周期也很簡單:
getSearchResults=()=> { const {searchTerm}=this.state; return searchTerm ? this.searchEngine.search(searchTerm) : [] // searchEngine.search is the expensive search algorithm };
我們來看看結(jié)果如何:
哎呀……很糟。在按住刪除鍵時的的確確能感覺到UI的停頓,因為鍵盤觸發(fā)delete事件太快了。
不過至少模糊搜索好用了:'anerican'正確地解釋成了'American'。但隨著搜索關(guān)鍵字的加長,兩個元素(輸入框和搜索結(jié)果)的渲染過程完全跟不上用戶輸入的速度,延遲非常大。
盡管我們的搜索算法的確很慢,但如此大的延遲并不是由于單次搜索時間太長導(dǎo)致的。這里還有另外一個現(xiàn)象需要理解:我們管它叫UI阻塞。理解這個現(xiàn)象需要深入了解Chrome的DevTools性能評測工具。
性能評測
可能你不熟悉這個工具,但是你要知道,熟練使用性能評測工具是深入理解JavaScript的最好方式。這不僅因為它能提供有用的信息幫你解決各種問題,而且按照時間顯示出JavaScript的執(zhí)行過程能夠幫助你更好地理解好的UI、React、JavaScript事件循環(huán)、同步異步執(zhí)行等概念。
在下面每一節(jié),我都會給出性能分析過程,以及從這些過程中推斷出的有趣的結(jié)論。這些數(shù)據(jù)可能會讓你眼花繚亂,但我會盡力給出合理的解釋!
首先評測一下在Autocomplete中按下兩個鍵時的情況:
X軸:時間,Y軸:按照類型排列的事件(用戶輸入、主線程函數(shù)調(diào)用)
理解基本的序列非常重要:
首先是用戶的鍵盤輸入(Key Characer)。這些輸入在JavaScript的主線程上觸發(fā)了Event(keypress)事件,該事件又觸發(fā)了我們的onChange處理函數(shù),該函數(shù)會調(diào)用setState(圖上看不見,因為它太小了,但它的位置在整個棧的最開頭附近)。這一系列動作標(biāo)志著重新計算的開始,計算setState對組件會產(chǎn)生何種影響。這一過程稱為更新,或者叫做重新渲染,它會調(diào)用幾個React生命周期方法,其中就包括render。這一切都發(fā)生在一個執(zhí)行棧(有時稱為“調(diào)用棧”或簡稱為“棧”),在圖中由每個Event (keypress)下方豎直排列的框表示。每個框都是執(zhí)行棧中的一次函數(shù)調(diào)用。
這里不要被render這個詞誤導(dǎo)了。React的渲染并不僅僅是在屏幕上繪制。渲染只是React用來計算被更新的元素應(yīng)當(dāng)如何顯示的過程。如果仔細(xì)查看第二個Event (keypress),就會發(fā)現(xiàn)在Event (keypress)框的外面有個小得幾乎看不見的綠條。放大一些就能看到這是對瀏覽器繪制的調(diào)用:
這才是UI更新被真正繪制到屏幕上,并在顯示器上顯示新幀的過程。而且,第一個Event (keypress)之后沒有繪制,只有第二個后面才有。
這說明,瀏覽器繪制(外觀上的更新)并不一定會在Event (keypress)事件發(fā)生并且React完成更新之后發(fā)生。
原因是JavaScript的事件循環(huán)和JavaScript對于任務(wù)隊列的優(yōu)先級處理。在React結(jié)束計算并將更新寫入DOM之后(稱為提交階段,發(fā)生在每個執(zhí)行棧末尾的地方),你可能會以為瀏覽器應(yīng)該開始繪制,將DOM更新顯示在屏幕上。但是在繪制之前,瀏覽器會檢查JavaScript事件隊列中是否還有其他任務(wù),有的任務(wù)會比繪制更優(yōu)先執(zhí)行。
當(dāng)JavaScript線程忙于渲染第一個keypress時,產(chǎn)生第二個用戶輸入(如鍵盤按下事件)就會出現(xiàn)這種情況(你可以看到第二個Key Character輸入發(fā)生在第一個keypress執(zhí)行棧依然在運(yùn)行時)。這就是前面提到的UI阻塞。第二個Event (keypress)阻塞了UI更新第一個keypress。
不幸的是,這會導(dǎo)致巨大的延遲。因為渲染本身非常慢(部分原因是因為渲染中包含了昂貴的搜索算法),如果用戶輸入非常快,那么很大可能會在前一個執(zhí)行棧結(jié)束之前輸入新的字符。這會產(chǎn)生新的Event (keypress),并且它的優(yōu)先級比瀏覽器繪制要高。所以繪制會不斷被用戶的輸入拖延。
不僅如此,甚至在用戶停止輸入后,隊列中依然滯留了許多keypress時間,React需要依次對每個keypress進(jìn)行計算。所以,即使在輸入結(jié)束后,也不得不等待這些針對早已不需要的searchTeams的搜索!
注意最后一個Key Character發(fā)生后,還有4個Event (keypress)滯留,瀏覽器需要處理完所有事件才能重繪。
改進(jìn)方法
為了解決這個問題,重要的是要理解好的UI的要素是什么。實際上,對于每次鍵盤輸入,用戶期待的視覺反饋包括兩個獨立的要素:
理解用戶的期望才能找到解決方案。盡管Google搜索快得像閃電一樣,但用戶無法立即收到搜索結(jié)果的事情也屢見不鮮。一些UI甚至?xí)谡埱笏阉鹘Y(jié)果時顯示加載進(jìn)度條。
重要的是要理解,對于反應(yīng)迅速的UI而言,第一種反饋(按下的鍵顯示在輸入框中)扮演了非常重要的角色。如果UI無法做到這一點,就會有嚴(yán)重的問題。
查看性能評測是解決問題的第一個提示。仔細(xì)觀察這些長長的執(zhí)行棧和render中包含的昂貴的search方法,我們會發(fā)現(xiàn),更新輸入框和搜索結(jié)果的一切操作都發(fā)生在同一個執(zhí)行棧內(nèi)。所以,兩者的UI更新都會被搜索算法阻塞。
但是,輸入框的更新不需要等待結(jié)果!它只需要知道用戶按下了哪個鍵,而不需要知道搜索結(jié)果是什么。如果我們有辦法控制事件執(zhí)行的順序,輸入框就有機(jī)會先更新UI,再去渲染搜索結(jié)果,這樣就能減少一些延遲。因此,我們的第一個優(yōu)化措施就是將輸入框的渲染從搜索結(jié)果的渲染中分離出來。
注意:熟悉Dan Abramov在React JSConf 2018上的演講的人應(yīng)該能回憶起這個場景。在他的幻燈片中,他設(shè)計了一個昂貴的更新操作,隨著輸入值的增加,屏幕上需要渲染的組件也越來越多。這里我們遇到的困難非常相似,只不過是隨著搜索關(guān)鍵字長度的增加,單個搜索函數(shù)的復(fù)雜度會增加而已。在Dan的演講中,他演示了時間切片(Time Slicing),這個React團(tuán)隊在開發(fā)中的功能也許可以解決這個問題!我們的嘗試會以相似的方案解決問題:找到一個方法來改變渲染的順序,防止昂貴的計算再次阻塞主線程的UI更新。
第三篇 異步渲染(componentDidUpdate)
注意:本篇討論的優(yōu)化最后以失敗告終了,但我認(rèn)為值得講一講,因為它能幫助我們更好地理解React的componentDidUpdate生命周期。如果你非常熟悉React,或者只想看看怎樣改善性能問題,那么可以直接跳到下一篇。
拆分組件
由于我們想把昂貴的搜索結(jié)果更新從輸入框更新中拆分出來,所以我們應(yīng)該自己設(shè)計一個組件,并放棄使用react-autocomplete庫提供的一站式解決方案Autocomplete:
//autocomplete.js render () { return ( <div> <input onChange={ e=> this.setState({searchTerm: e.target.value})} value={this.state.searchTerm}/> <SearchResults searchEngine={this.props.searchEngine} searchTerm={this.state.searchTerm}/> </div> ) }
然后在SearchResults中,我們需要異步觸發(fā)searchEngine.search(searchTerm),而不應(yīng)該在更新searchTerm的同一個渲染中進(jìn)行。
我最初的想法是利用SearchResults的componentDidUpdate,讓searchEngine異步工作,因為聽上去這個方法似乎是在更新之后異步執(zhí)行的。
//searchResults.js componentDidUpdate(prevProps) { const {searchTerm, searchEngine}=this.props; if(searchTerm && searchTerm !==prevProps.searchTerm) { this.setState({ searchResults: searchEngine.search(searchTerm) }) } } render () { return <ReactVirtualizedList searchResults={this.state.searchResults}/> }
我們將昂貴的searchEngine移動到了componentDidUpdate中,這樣就不用在render方法中調(diào)用,而是等到更新之后再執(zhí)行。我希望在輸入框更新之后的另一個執(zhí)行棧中執(zhí)行render,并兩者之間執(zhí)行一次繪制。我想象的新的更新生命周期如下:
很不幸,認(rèn)為componentDidUpdate會在瀏覽器更新之后運(yùn)行是一個常見的誤解。我們來看看這個方法的性能評測:
看到問題了嗎?componentDidUpdate跟最初的keypress事件是在同一個執(zhí)行棧上執(zhí)行的
componentDidUpdate并不會在繪制結(jié)束后執(zhí)行,因此執(zhí)行棧跟上一篇一樣昂貴,我們只不過是將昂貴的search方法移動到了不同位置而已。盡管這個解決方案并不能改善性能,但我們可以借此機(jī)會理解React的生命周期componentDidUpdate的具體行為。
雖然componentDidUpdate不會在另一個執(zhí)行棧上運(yùn)行,但它確實是在React更新完組件狀態(tài)并將更新后的DOM值提交之后才執(zhí)行的。盡管這些更新后的DOM值還沒有被瀏覽器繪制,它們依然反映了更新后的UI應(yīng)有的樣子。所以,任何componentDidupdate內(nèi)執(zhí)行的DOM查詢都能訪問到更新后的值。所以,組件確實更新了,只是瀏覽器中看不見而已。
所以,如果想要做DOM計算,一般都應(yīng)該在componentDidUpdate中進(jìn)行。在這里很方便根據(jù)布局改變進(jìn)行更新,比如根據(jù)新的布局方式計算元素的位置或大小,然后更新狀態(tài)等。
如果componentDidUpdate每次觸發(fā)改變布局的更新時都要等待實際的瀏覽器繪制,那么用戶體驗會非常糟糕,因為用戶可能會在布局改變時看到兩次屏幕閃爍。
注(React Hooks):這個差異也有助于理解新的useEffect和useLayoutEffect鉤子。useEffect就是我們在這里嘗試實現(xiàn)的效果。它會讓代碼在另一個執(zhí)行棧中運(yùn)行,從而在執(zhí)行之前瀏覽器可以進(jìn)行繪制。而useLayoutEffect更像是componentDidUpdate,允許你在DOM更新之后、瀏覽器繪制之前執(zhí)行代碼。
第四篇 異步渲染(setTimeout)
上一篇我們拆分了組件:
//autocomplete.js render () { return ( <div> <input onChange={ e=> this.setState({searchTerm: e.target.value})} value={this.state.searchTerm}/> <SearchResults searchEngine={this.props.searchEngine} searchTerm={this.state.searchTerm}/> </div> ) }
但我們沒能讓昂貴的searchEngine在另一個執(zhí)行棧上運(yùn)行。那么,還有什么辦法能實現(xiàn)這一點呢?
有兩個常見的方法可以設(shè)置異步調(diào)用:Promise和setTimeout。
Promise
//searchResults.js componentDidUpdate(prevProps) { const {searchTerm, searchEngine}=this.props; if(searchTerm && searchTerm !==prevProps.searchTerm) { /* stick the update with the expensive search method into a promise callback: */ Promise.resolve().then(()=> { this.setState({ searchResults: searchEngine.search(searchTerm) }) }) } } render () { return <ReactVirtualizedList searchResults={this.state.searchResults}/> }
我們來看看性能評測:
(anonymous)是Promise.then()回調(diào)函數(shù)
又失敗了!
理論上Promsie的回調(diào)函數(shù)是異步的,因為它們不會同步執(zhí)行,但實際上還是在同一個執(zhí)行棧中運(yùn)行的。
仔細(xì)看看性能評測就會發(fā)現(xiàn),回調(diào)函數(shù)被放在了Run Microtasks下,因為Promise的回調(diào)函數(shù)被當(dāng)作了微任務(wù)。瀏覽器通常會在完成正常的棧之后檢查并運(yùn)行微任務(wù)。
更多信息:Jake Archibald有一篇非常好的演講(https://medium.com/r/?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DcCOL7MC4Pl0),解釋了JavaScript事件循環(huán)在微任務(wù)方面的處理,還深入討論了許多我涉及到的話題。
盡管了解這一點很好,但并沒有解決問題。我們需要新的執(zhí)行棧,這樣瀏覽器才有機(jī)會在新的執(zhí)行棧開始之前進(jìn)行繪制。
setTimeout
//searchResults.js componentDidUpdate(prevProps) { const {searchTerm, searchEngine}=this.props; if(searchTerm && searchTerm !==prevProps.searchTerm) { /* stick the update with the expensive search method into a setTimeout callback: */ const setTimeoutCallback=()=> { this.setState({ searchResults: searchEngine.search(searchTerm) }) } setTimeout(setTimeoutCallback) } } render () { return <ReactVirtualizedList searchResults={this.state.searchResults}/> }
性能評測:
繪制很難看到,它太小了,但藍(lán)線的位置的確發(fā)生了繪制
哈哈!成功了!注意到這里有兩個執(zhí)行棧:Event (keypress)和Timer Fired (searchResults.js:49)。兩個棧之間發(fā)生了一次繪制(藍(lán)線的位置)。這正是我們想要的!來看看漂亮的UI!
按住刪除鍵時依然有明顯的延遲
有很大的改進(jìn),但依然很令人失望……這個頁面的確變好了,但依然能感覺到UI的延遲。我們來仔細(xì)看看性能測試。
我們需要一些技巧才能分析這個性能測試報告并找出延遲的原因。使用性能報告有幾個小技巧:
性能評測工具著重顯示了兩個特別有用的Key Input Interactions,每個都有不同的性能度量:
理想狀態(tài)下,Key Down交互應(yīng)該非常短,因為如果JavaScript主線程沒有被阻塞的話,事件應(yīng)該在鍵盤按下之后立即出發(fā)。所以,看到長長的Key Down就意味著發(fā)生了UI阻塞問題。
在本例中,長長的Key Down的原因是它們觸發(fā)時,主線程還在被昂貴的setTimeoutCallback阻塞,比如最后的Key Down。很不幸,Key Down發(fā)生時,運(yùn)行昂貴的search的setTimeoutCallback剛剛開始,也就是說,Key Down只有等待search的計算結(jié)束才能觸發(fā)事件。這個評測中的search方法大約花了330毫秒,也就是1/3秒。從優(yōu)秀的UI角度而言,1/3秒的主線程阻塞實在太長了。
特別引起我注意的是最后一個Key Character。盡管它關(guān)聯(lián)了Key Down的結(jié)束并且觸發(fā)了Event (keypress),瀏覽器并沒有繪制新的searchTerm,而是執(zhí)行了另一個setTimeoutCallback。這就是Key Character交互花了兩倍時間的原因。
這一點著實讓我大跌眼鏡。將search移動到setTimeoutCallback的目的,就是讓瀏覽器能夠在調(diào)用setTimeoutCallback之前進(jìn)行繪制。但是在最后的Event (keypress)之前依然沒有繪制(藍(lán)線的位置)。
結(jié)論是,我們不能依賴于瀏覽器的隊列機(jī)制。顯然瀏覽器并不一定將繪制排在超時回調(diào)之前。如果超時回調(diào)需要占用主線程330毫秒,那么也會阻礙主線程,導(dǎo)致延遲。
第五篇 多線程
在上一篇中,我們拆分了組件,并成功地使用setTimeout將昂貴的search移動到了另一個執(zhí)行棧中,因此瀏覽器無需等待 search完成,就可以繪制輸入框的更新:
//autocomplete.js render () { return ( <div> <input onChange={ e=> this.setState({searchTerm: e.target.value})} value={this.state.searchTerm}/> <SearchResults searchEngine={this.props.searchEngine} searchTerm={this.state.searchTerm}/> </div> ) } //searchResults.js componentDidUpdate(prevProps) { const {searchTerm, searchEngine}=this.props; if(searchTerm && searchTerm !==prevProps.searchTerm) { const setTimeoutCallback=()=> { this.setState({ searchResults: searchEngine.search(searchTerm) }) } setTimeout(setTimeoutCallback) } } render () { return <ReactVirtualizedList searchResults={this.state.searchResults}/> }
不幸的是,這依然沒有解決每個searchTerm導(dǎo)致search阻塞主線程330毫秒并導(dǎo)致UI延遲的問題。
JavaScript的單線程實在太糟糕了……突然我想到了一個方法。
最近我在閱讀漸進(jìn)式Web應(yīng)用,其中有人使用Service Worker來實現(xiàn)一些過程,比如在另一個線程中進(jìn)行緩存。于是我開始學(xué)習(xí)Service Worker。這是一個全新的API,我需要花點時間來學(xué)習(xí)。
但在學(xué)習(xí)之前我想先通過實驗來驗證一下增加額外的線程是否真的能夠提高性能。
用服務(wù)器端來模擬第二個線程
我以前就做過搜索和自動補(bǔ)齊提示的功能,但這次特別困難的原因是需要完全在前端實現(xiàn)。以前我做過用API來獲取搜索結(jié)果。其中一種思路是,API和運(yùn)行API的服務(wù)器實際上相當(dāng)于前端應(yīng)用利用的另一個線程。
于是,我想到可以做一個簡單的node服務(wù)器讓searchTerm訪問,從而實現(xiàn)在另一個線程中運(yùn)行昂貴的搜索。這個功能非常容易實現(xiàn),因為這個項目中已經(jīng)設(shè)置過開發(fā)用的服務(wù)器了。所以我只需添加一個新的路徑:
app.route('/prep-staging/testSearch') .post(bodyParser.json(), (req, res)=> { const {data, searchTerm}=req.body const engine=new SearchEngine(data) const searchResults=engine.search(searchTerm) res.send({searchResult}) })
然后將SearchResults中的setTimeout改成fetch:
//searchResults.js componentDidUpdate(prevProps) { const {searchTerm, searchEngine, data}=this.props; if(searchTerm && searchTerm !==prevProps.searchTerm) { /* ping the search route with the searchTerm and update state with the results when they return: */ fetch(`testSearch`, { method: 'POST', body: JSON.stringify({data, searchTerm}), headers: { 'content-type': 'application/json' } }) .then(r=> r.json()) .then(resp=> this.setState({searchResults: resp.searchResults})) } } render () { return <ReactVirtualizedList searchResults={this.state.searchResults}/> }
現(xiàn)在是見證奇跡的時刻!
注意看刪除!
太棒了!輸入框的更新幾乎非常完美。而另一方面,搜索結(jié)果的更新依然非常緩慢,但沒關(guān)系!別忘了我們的首要任務(wù)就是讓用戶在輸入時盡快獲得反饋。我們來看看性能測試報告:
注意主線程中間漂亮的空白,它不再是之前層層疊疊的樣子了。性能優(yōu)化中經(jīng)常會遇到這種情況。主線程中的空白越多越好!
從頂端可以看到testSearch花費的時間。最長的一個實際上花了800毫秒,讓我很吃驚,但仔細(xì)想想就會發(fā)現(xiàn),并不是search花了這么長時間,而是我們的Node服務(wù)器也是單線程的。它在第一個搜索完成之前無法開始另一個搜索。由于輸入比搜索快得多,所以搜索會進(jìn)入隊列然后被延遲。搜索函數(shù)實際花費的時間是前一個搜索完成之后的部分,大約315毫秒。
總的來說,將昂貴的任務(wù)放到服務(wù)器線程中,就可以將堆疊的棧移動到服務(wù)器上。所以,盡管依然有改進(jìn)的空間,但主線程看起來非常好,UI的響應(yīng)速度也更快了!
我們已經(jīng)證明這個思路是正確的,現(xiàn)在來實現(xiàn)吧!
做了一點研究后我發(fā)現(xiàn),Server Worker并不是正確的選擇,因為它不兼容Internet Explorer。幸運(yùn)的是,它還有個近親,叫做Web Worker,能兼容所有主流服務(wù)器,API更簡單,而且能完成我們需要的功能!
第六篇 Web Worker
Web worker能夠在JavaScript的主線程之外的另一個線程上運(yùn)行代碼,每個Web worker都由一個腳本文件啟動。啟動方式非常簡單:
//searchResults.js export default class SearchResults extends React.Component { constructor (props) { super(); this.state={ searchResults: [], } //initiate the webworker: this.webWorker=new Worker('...path to webWorker.js') //pass it the 13,000 item search data to initialize the searchEngine with: this.webWorker.postMessage({data: props.data}) //assign the handler that will accept the searchResults when it sends them back: this.webWorker.onmessage=this.handleResults } componentDidUpdate(prevProps) { const {searchTerm}=this.props; if(searchTerm && searchTerm !==prevProps.searchTerm) { //change our async search request to a .postMessage, the messaging API of webWorkers: this.webWorker.postMessage({searchTerm}) } } handleResults=(e)=> { const {searchResults}=e.data this.setState({ searchResults }) } render () { return <ReactVirtualizedList searchResults={this.state.searchResults}/> } }
下面是webWorker.js腳本,在SearchResults的構(gòu)造函數(shù)中進(jìn)行初始化:
//webWorker.js self.importScripts('...the search engine script, provides the SearchEngine constructor'); let searchEngine; let cache={} //thought I would add a simple cache... Wait till you see those deletes now :) function initiateSearchEngine (data) { //initiate the search engine with the 13,000 item data set searchEngine=new SearchEngine(data); //reset the cache on initiate just in case cache={}; } function search (searchTerm) { const cachedResult=cache[searchTerm] if(cachedResult) { self.postMessage(cachedResult) return } const message={ searchResults: searchEngine.search(searchTerm) }; cache[searchTerm]=message; //self.postMessage is the api for sending messages to main thread self.postMessage(message) } /*self.onmessage is where we define the handler for messages recieved from the main thread*/ self.onmessage=function(e) { const {data, searchTerm}=e.data; /*We can determine how to respond to the .postMessage from SearchResults.js based on which data properties it has:*/ if(data) { initiateSearchEngine(data) } else if(searchTerm) { search(searchTerm) } }
可以看到,我還加了些額外的代碼。這里我加了緩存,這樣之前搜索過的searchTerms就可以立即返回結(jié)果了。如此一來,最耗性能的用戶交互(常按刪除鍵)的效率就提高了。
我們來看看運(yùn)行情況:
太棒了……非常快!這看起來很不錯啊,實話說,做到這個樣子就可以直接發(fā)布了!
但是,現(xiàn)在就發(fā)布多沒勁啊……
從用戶體驗的角度來說,這個UI已經(jīng)非常好了。如果仔細(xì)觀察,其實依然能看到搜索結(jié)果的延遲,但幾乎察覺不到……不過幸運(yùn)的是,從性能測試報告中可以看到低效率的地方:
請無視灰色的條,我也不知道它們是怎么來的。
報告中最左側(cè)的線程是我們關(guān)注的線程。Main線程已經(jīng)折疊了,因為里面只有大量的空白,意味著主線程的性能非常好,不會成為任何主要的性能瓶頸。
可以看到,Worker線程里堆滿了search調(diào)用。從最后一個Key Character輸入(藍(lán)線位置)之后就會看到其后果。Worker線程中的最后一個search實際上推遲了3個search才返回。測量一下會發(fā)現(xiàn),延遲大約有850毫秒。而且大部分都是不必要的,因為那三個search都沒用,我們不再需要它們返回的結(jié)果了。
你也許在想:“這已經(jīng)很好了!再優(yōu)化下去性價比不高啊!”
我不這樣認(rèn)為。首先,不要低估嘗試新事物和探索帶來的價值。如果你從來沒做過探索,就很可能無法評價是否值得,因為你不知道你能有哪些收獲,以及你將投入多少時間和努力。所以,我認(rèn)為這種探索帶來的經(jīng)驗和知識是無價的。隨著知識的積累,你能更好地評價投入的時間是否值得,以及是否應(yīng)該考慮這些問題。你可以做出更好的決定!
其次,別忘了這并不是過早優(yōu)化。這些優(yōu)化都是根據(jù)性能評價做出的,我們可以測量出效果。不管怎樣評價,如果能改善850毫秒的延遲,那都是非常重大的改進(jìn)。
最后(但不是唯一),別忘了移動端!雖然我不會在本文中介紹,但我在研究這個問題時,我也跟蹤了移動端的性能,現(xiàn)在的條件下依然有能察覺得到的性能延遲。
不管怎么說,我們來解決這個問題!
第七篇 確保searchTerm
前面的性能評測揭示的最明顯的問題就是,即使對于無用的searchTerm也會運(yùn)行昂貴的搜索。所以目前的解決方案之一就是在執(zhí)行昂貴的搜索之前確保searchTerm是最新的。只要在webWorker腳本中加入confirmSearchTerm就可以非常容易地實現(xiàn):
//webWorker.js self.importScripts('...the search engine script, provides the SearchEngine constructor'); let searchEngine; let cache={} function initiateSearchEngine (data) { searchEngine=new SearchEngine(data); cache={}; } function search (searchTerm) { const cachedResult=cache[searchTerm] if(cachedResult) { self.postMessage(cachedResult) return } const message={ searchResults: searchEngine.search(searchTerm) }; cache[searchTerm]=message; self.postMessage(message) } function confirmSearchTerm (searchTerm) { self.postMessage({confirmSearchTerm: searchTerm}) } self.onmessage=function(e) { const {data, searchTerm, confirmed}=e.data; if(data) { initiateSearchEngine(data) } else if(searchTerm) { /*check if the searchTerm is confirmed, if not, send a confirmSearchTerm message to compare the searchTerm with the latest value on the main thread */ confirmed ? search(searchTerm) : confirmSearchTerm(searchTerm) } }
這里還給SearchResults handleResults加了個額外的條件,監(jiān)聽confirmSearchTerm的請求:
//searchResults.js export default class SearchResults extends React.Component { constructor (props) { super(); this.state={ searchResults: [], } this.webWorker=new Worker('...path to webWorker.js') this.webWorker.postMessage({data: props.data}) this.webWorker.onmessage=this.handleResults } componentDidUpdate(prevProps) { const {searchTerm}=this.props; if(searchTerm && searchTerm !==prevProps.searchTerm) { this.webWorker.postMessage({searchTerm}) } } handleResults=(e)=> { const {searchResults, confirmSearchTerm}=e.data; /* check if confirmSearchTerm property was sent, if so compare it to the latest searchTerm and send back a confirmed searchTerm message */ if (confirmSearchTerm && confirmSearchTerm===this.props.searchTerm) { this.webWorker.postMessage({ searchTerm: this.props.searchTerm, confirmed: true }) } else if (searchResults) { this.setState({ searchResults }) } } render () { return <ReactVirtualizedList searchResults={this.state.searchResults}/> } }
我們來看看性能評測,看看有沒有改進(jìn):
很難看出結(jié)果,因為它們運(yùn)行得太快了,但在每次Worker的執(zhí)行棧之前都會執(zhí)行confirmSearchTerm(見藍(lán)線)。
說明的確管用了:Worker在每次運(yùn)行昂貴的search方法之前都會確認(rèn)搜索是否必要。而且可以看到,頂部的橙色部分有4個Key Character輸入,但只運(yùn)行了三個搜索。這里我們成功地去掉了一個不必要的搜索,節(jié)約了最多330毫秒。但之前我們看到,多個額外的搜索會進(jìn)入隊列然后再不必要地運(yùn)行,而現(xiàn)在我們完全避免了這個問題。所以,節(jié)約的時間非常顯著,特別是在移動端上。
但仔細(xì)觀察就會發(fā)現(xiàn)我們依然在浪費時間:
最后一個搜索使用了最新的searchTerm,但依然要至少等待當(dāng)前的搜索完成后才能開始。浪費了84毫秒(藍(lán)色高亮部分)!據(jù)我所知,執(zhí)行棧一旦開始就無法取消。那么我們是不是無計可施了呢?
第八篇 Web Worker陣列
如果多加一個線程的效果很好,那么加4個會怎樣?
實話實說,現(xiàn)在這些只是出于興趣……但說真的,最后這次優(yōu)化確實在移動端上帶來了人眼能察覺到的改善……
無論怎樣,現(xiàn)在我打算用Web Worker陣列!
為了出這份報告,我用人類最快的速度輸入的!
圖中可以看到,頂端每個橙色的Key Character都有一個ww worker線程,能立即開始搜索(橙色條)。不再需要等待前一個搜索結(jié)束。而且每個ww在結(jié)束后就能用于下一次搜索。
這是因為我們設(shè)置了workerArray.js這個web worker作為分發(fā)器。圖中看不到,因為它執(zhí)行得太快了,但對于每個Key Character,workerArray都會執(zhí)行一個微笑的執(zhí)行棧,用來處理主線程傳來的搜索請求消息,然后分發(fā)給第一個可用的ww worker。
我們成功地解決了“搜索堆”的問題。可以認(rèn)為用增加車道的方式解決了交通擁堵。
為什么用4個搜索worker呢?因為我在測試時的輸入速度從來沒能到過需要第五個worker的速度(增加worker帶來的改進(jìn)非常微小)。
結(jié)果發(fā)現(xiàn),從Key Character輸入到search執(zhí)行之間沒有任何延遲。除非找到另一個更有效的搜索算法,否則我們已經(jīng)成功地移除了所有性能瓶頸。
別忘了,我并沒有說這是最佳的解決方案。是否每臺手機(jī)都能處理4個web worker?性能改善是否值得付出這些額外的代碼復(fù)雜度?是否還有其他安全方面的考量,導(dǎo)致代碼更加復(fù)雜?
這些問題都非常重要,正是這些問題會最終引出這樣的解決方案。
但至少現(xiàn)在,這個Web worker陣列非常優(yōu)秀!我們來看看實際效果:
感謝你耐心地閱讀完所有的篇節(jié)!
如果說這一系列優(yōu)化有什么感想的話,那就是:
如果你有興趣,可以看看下面Web worker陣列的代碼。
課外作業(yè)
下面的代碼中有個小問題。需要一個非常微小的修改才能使它更強(qiáng)壯,最多兩行代碼。你能找到問題所在并修復(fù)嗎?
//searchResults.js export default class SearchResults extends React.Component { constructor (props) { super(); this.state={ searchResults: [], } //initiate the worker array: this.workerArray=new WorkerArrayController({ data: props.data, handleResults: this.handleResults, arraySize: 4 }); } componentDidUpdate(prevProps) { const {searchTerm}=this.props; if(searchTerm && searchTerm !==prevProps.searchTerm) { this.workerArray.search({searchTerm}) } } handleResults=(e)=> { const {searchResults}=e.data this.setState({ searchResults }) } componentWillUnmount () { this.workerArray.terminate(); } render () { return <ReactVirtualizedList searchResults={this.state.searchResults}/> } }
SearchResults組件初始化WorkerArrayController。
//workerArrayController.js export default class WorkerArrayController { constructor ({data, handleResults, arraySize}) { this.workerArray=new Worker('... path to workerArray.js'); let i=1; this.webWorkers={}; while (i <=arraySize) { const workerName=`ww${i}`; this.webWorkers[workerName]=new Worker(`...path to ww1.js`); /* Creates a MessageChannel for each worker and passes that channel's ports to both workerArray dispatcher and the worker so they can communicate with each other */ const channel=new MessageChannel(); this.workerArray.postMessage({workerName}, [channel.port1]); this.webWorkers[workerName].postMessage({data}, [channel.port2]); i++; } this.workerArray.onmessage=handleResults; } search=(searchTerm)=> { this.workerArray.postMessage({searchTerm}); } terminate() { this.workerArray.terminate(); for (const workerName in this.webWorkers) { this.webWorkers[workerName].terminate(); } } }
WorkerArrayController用4個ww初始化workerArray web worker,并傳遞MessageChannel端口給它們,這樣它們能夠互相通信。
//workerArray.js const ports={}; let cache={}; let queue; function initiatePort (workerName, port) { ports[workerName]=port; const webWorker=ports[workerName]; webWorker.inUse=false; webWorker.onmessage=function handleResults (e) { const {searchTerm, searchResults}=e.data; const message={searchTerm, searchResults}; /* If all workers happen to be inUse, the message gets saved to the the queue and passed to the first worker that finishes */ if(queue) { webWorker.postMessage(queue); webWorker.inUse=true; queue=null; } else { webWorker.inUse=false; } cache[searchTerm]=message; self.postMessage(message); } } function dispatchSearchRequest (searchTerm) { const cachedResult=cache[searchTerm]; if(cachedResult) { self.postMessage(cachedResult); return } const message={searchTerm}; for (const workerName in ports) { const webWorker=ports[workerName]; if(!webWorker.inUse) { webWorker.postMessage(message); webWorker.inUse=true; return } } queue=message; } self.onmessage=function (e) { const {workerName, searchTerm}=e.data; if(workerName) { initiatePort(workerName, e.ports[0]); } else if(searchTerm) { dispatchSearchRequest(searchTerm); } }
workerArray初始化端口對象用于通信,并跟蹤每個ww worker。它還初始化了緩存和隊列,萬一所有端口都被占用的情況下用來跟蹤最新的searchTerm請求。
//ww1.js self.importScripts('...the search engine script, provides the SearchEngine constructor'); let searchEngine; let port; function initiate (data, port) { searchEngine=new SearchEngine(data); port=port; port.onmessage=search; } /* search is attached to the port as the message handler so it runs when communicating with the workerArray only */ function search (e) { const {searchTerm}=e.data; const message={ searchResults: searchEngine.search(searchTerm) }; port.postMessage(message) } /* self.onmessage is the handler that responds to messages from the main thread, which only fires during initiation */ self.onmessage=function(e) { const {data}=e.data; initiate(data, e.ports[0]); }
原文:Secrets of JavaScript: A tale of React, performance optimization and multi-threading
本文為 CSDN 翻譯
eact是facebook出的一款針對view視圖層的library庫。react遵循單向數(shù)據(jù)流的機(jī)制。目前我們學(xué)的是react17.x的版本
https://react.docschina.org/
<Outlet></Outlet>
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。