在以下場景中,父組件和子組件通常會重新渲染:
在同一組件或父組件中調用 setState 時。
從父級收到的“props”的值發生變化。
調用組件中的 forceUpdate。
下面是提升 React 應用性能的 21 個技巧。
1.使用純組件
如果 React 組件為相同的狀態和 props 渲染相同的輸出,則可以將其視為純組件。
對于像 this 的類組件來說,React 提供了 PureComponent 基類。擴展 React.PureComponent 類的類組件被視為純組件。
它與普通組件是一樣的,只是 PureComponents 負責 shouldComponentUpdate——它對狀態和 props 數據進行淺層比較(shallow comparison)。
如果先前的狀態和 props 數據與下一個 props 或狀態相同,則組件不會重新渲染。
什么是淺層渲染?
在對比先前的 props 和狀態與下一個 props 和狀態時,淺層比較將檢查它們的基元是否有相同的值(例如:1 等于 1 或真等于真),還會檢查更復雜的 JavaScript 值(如對象和數組)之間的引用是否相同。
比較基元和對象引用的開銷比更新組件視圖要低。
因此,查找狀態和 props 值的變化會比不必要的更新更快。
setstate 在一秒的間隔之后被調用,這將重新觸發組件的視圖渲染。由于初始 props 和新 props 的值相同,因此組件(PureChildComponent)不會被重新渲染。
狀態的淺層比較表明 props 或狀態的數據沒有變化,因此不需要渲染組件,從而提升了性能。
2.使用 React.memo 進行組件記憶
React.memo 是一個高階組件。
它很像 PureComponent,但 PureComponent 屬于 Component 的類實現,而“memo”則用于創建函數組件。
這里與純組件類似,如果輸入 props 相同則跳過組件渲染,從而提升組件性能。
它會記憶上次某個輸入 prop 的執行輸出并提升應用性能。即使在這些組件中比較也是淺層的。
你還可以為這個組件傳遞自定義比較邏輯。
用戶可以用自定義邏輯深度對比(deep comparison)對象。如果比較函數返回 false 則重新渲染組件,否則就不會重新渲染。
如果我們將對象引用作為 props 傳遞給 memo 組件,則需要一些自定義登錄以進行比較。在這種情況下,我們可以將比較函數作為第二個參數傳遞給 React.memo 函數。
假設 props 值(user)是一個對象引用,包含特定用戶的 name、age 和 designation。
這種情況下需要進行深入比較。我們可以創建一個自定義函數,查找前后兩個 props 值的 name、age 和 designation 的值,如果它們不相同則返回 false。
這樣,即使我們將參考數據作為 memo 組件的輸入,組件也不會重新渲染。
function CustomisedComponen(props) {
return (
<div>
<b>User name: {props.user.name}</b>
<b>User age: {props.user.age}</b>
<b>User designation: {props.user.designation}</b>
</div>
)
}
function userComparator(previosProps, nextProps) {
if(previosProps.user.name == nextProps.user.name ||
previosProps.user.age == nextProps.user.age ||
previosProps.user.designation == nextProps.user.designation) {
return false
} else {
return true;
}
}
var memoComponent = React.memo(CustomisedComponent, userComparator);
上面的代碼提供了用于比較的自定義邏輯
3、使用 shouldComponentUpdate 生命周期事件
這是在重新渲染組件之前觸發的其中一個生命周期事件。
可以利用此事件來決定何時需要重新渲染組件。如果組件 props 更改或調用 setState,則此函數返回一個 Boolean 值。
在這兩種情況下組件都會重新渲染。我們可以在這個生命周期事件中放置一個自定義邏輯,以決定是否調用組件的 render 函數。
這個函數將 nextState 和 nextProps 作為輸入,并可將其與當前 props 和狀態做對比,以決定是否需要重新渲染。
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false
}
這里即使組件中其他參數發生變化也不會影響應用的視圖。
shouldComponentUpdate 將輸入參數作為狀態和 props 的新值。
我們可以比較 name 和 age 的當前值和新值。有任何一個發生變化就可以觸發重新渲染。
從 shouldComponentUpdate 傳遞 true 就意味著可以重新渲染組件,反之亦然。所以正確使用 shouldComponentUpdate 就可以優化應用組件的性能。
對比過初始狀態和 props 后我們就可以決定是否需要重新渲染組件。這樣可以減少重新渲染的需求來提升性能。
4、懶加載組件
導入多個文件合并到一個文件中的過程叫打包,使應用不必導入大量外部文件。
所有主要組件和外部依賴項都合并為一個文件,通過網絡傳送出去以啟動并運行 Web 應用。
這樣可以節省大量網絡調用,但這個文件會變得很大,消耗大量網絡帶寬。
應用需要等待這個文件的加載和執行,所以傳輸延遲會帶來嚴重的影響。
為了解決這個問題,我們引入代碼拆分的概念。
像 webpack 這樣的打包器支持就支持代碼拆分,它可以為應用創建多個包,并在運行時動態加載,減少初始包的大小。
為此我們使用 Suspense 和 lazy。
if(this.props.name == "Mayank") {
ComponentToLazyLoad = lazy(() => import("./mayankComponent"));
} else if(this.props.name == "Anshul") {
ComponentToLazyLoad = lazy(() => import("./anshulComponent"));
}
return (
<div>
<h1>This is the Base User: {this.state.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<ComponentToLazyLoad />
</Suspense>
</div>
)
上面的代碼中有一個條件語句,它查找 props 值,并根據指定的條件加載主組件中的兩個組件。
我們可以按需懶惰加載這些拆分出來的組件,增強應用的整體性能。
假設有兩個組件 WelcomeComponent 或 GuestComponents,我們根據用戶是否登錄而渲染其中一個。
我們可以根據具體的條件延遲組件加載,無需一開始就加載兩個組件。
if(this.props.username !== "") {
const WelcomeComponent = lazy(() => import("./welcomeComponent"));
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<WelcomeComponent />
</Suspense>
</div>
)
} else {
const GuestComponent = lazy(() => import("./guestComponent"));
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<GuestComponent />
</Suspense>
</div>
)
}
在上面的代碼中我們沒有預加載 WelcomeCompoment 和 GuestComponents 這兩個組件,而是進行一個條件檢查。
如果用戶名存在(或相反),我們就根據指定的條件決定將某個組件作為單獨的包加載。
這個方法的好處
主包體積變小,消耗的網絡傳輸時間更少。
動態單獨加載的包比較小,可以迅速加載完成。
我們可以分析應用來決定懶加載哪些組件,從而減少應用的初始加載時間。
5、使用 React Fragments 避免額外標記
使用 Fragments 減少了包含的額外標記數量,這些標記只是為了滿足在 React 組件中具有公共父級的要求。
用戶創建新組件時,每個組件應具有單個父標簽。父級不能有兩個標簽,所以頂部要有一個公共標簽。所以我們經常在組件頂部添加額外標簽,例如:
在上面指定的組件中,我們需要一個額外的標簽為要渲染的組件提供公共父級。
除了充當組件的父標簽之外,這個額外的 div 沒有其他用途。
在頂層有多個標簽會導致以下錯誤:
要解決此問題,我們可以將元素包含在片段(fragement)中。
片段不會向組件引入任何額外標記,但它仍然為兩個相鄰標記提供父級,因此滿足在組件頂級具有單個父級的條件。
<>
<h1>This is the Header Component</h1>
</>
上面的代碼沒有額外的標記,因此節省了渲染器渲染額外元素的工作量。
6、不要使用內聯函數定義
如果我們使用內聯函數,則每次調用“render”函數時都會創建一個新的函數實例。
當 React 進行虛擬 DOM diffing 時,它每次都會找到一個新的函數實例;因此在渲染階段它會會綁定新函數并將舊實例扔給垃圾回收。
因此直接綁定內聯函數就需要額外做垃圾回收和綁定到 DOM 的新函數的工作。
<input type="button" onClick={(e) => { this.setState({inputValue: e.target.value}) }} value="Click For Inline Function" />
上面的函數創建了內聯函數。每次調用 render 函數時都會創建一個函數的新實例,render 函數會將該函數的新實例綁定到該按鈕。
此外最后一個函數實例會被垃圾回收,大大增加了 React 應用的工作量。
所以不要用內聯函數,而是在組件內部創建一個函數,并將事件綁定到該函數本身。這樣每次調用 render 時就不會創建單獨的函數實例了,參考組件如下。
setNewStateData = (event) => {
this.setState({
inputValue: e.target.value
})
}
<input type="button" onClick={this.setNewStateData} value="Click For Inline Function" />
7、避免 componentWillMount() 中的異步請求
componentWillMount 是在渲染組件之前調用的。
這個函數用的不多,可用來配置組件的初始配置,但使用 constructor 方法自己也能做到。
該方法無法訪問 DOM 元素,因為組件還沒掛載上來。
一些開發人員認為這個函數可以用來做異步數據 API 調用,但其實這沒什么好處。
由于 API 調用是異步的,因此組件在調用 render 函數之前不會等待 API 返回數據。于是在初始渲染中渲染組件時沒有任何數據。
componentWillMount() {
axios.get("someResourceUrl").then((data) => {
this.setState({
userData: data
});
});
}
在上面的代碼中,我們正在進行異步調用以獲取數據。由于數據調用是異步的,需要一段時間才能獲取到。
在檢索數據時 React 會觸發組件的 render 函數。因此第一個調用的渲染仍然不包含它所需的數據。
這樣一開始渲染組件沒有數據,然后檢索數據,調用 setState,還得重新渲染組件。在 componentWillMount 階段進行 AJAX 調用沒有好處可言。
我們應避免在此函數中發出 Async 請求。這些函數和調用可以延遲到 componentDidMount 生命周期事件里。
注意:React 16.3 不推薦使用 componentWillMount。如果你使用的是最新版本的 React,請避免使用這個生命周期事件。
8、在 Constructor 的早期綁定函數
當我們在 React 中創建函數時,我們需要使用 bind 關鍵字將函數綁定到當前上下文。
綁定可以在構造函數中完成,也可以在我們將函數綁定到 DOM 元素的位置上完成。
兩者之間似乎沒有太大差異,但性能表現是不一樣的。
handleButtonClick() {
alert("Button Clicked: " + this.state.name)
}
render() {
return (
<>
<input type="button" value="Click" onClick={this.handleButtonClick.bind(this)} />
</>
)
}
在上面的代碼中,我們在 render 函數的綁定期間將函數綁定到按鈕上。
上面代碼的問題在于,每次調用 render 函數時都會創建并使用綁定到當前上下文的新函數,但在每次渲染時使用已存在的函數效率更高。優化方案如下:
constructor() {
this.state = {
name: "Mayank"
}
this.handleButtonClick = this.handleButtonClick.bind(this)
}
handleButtonClick() {
alert("Button Clicked: " + this.state.name)
}
render() {
return (
<>
<input type="button" value="Click" onClick={this.handleButtonClick} />
</>
)
}
最好在構造函數調用期間使用綁定到當前上下文的函數覆蓋 handleButtonClick 函數。
這將減少將函數綁定到當前上下文的開銷,無需在每次渲染時重新創建函數,從而提高應用的性能。
9、箭頭函數與構造函數中的綁定
處理類時的標準做法就是使用箭頭函數。使用箭頭函數時會保留執行的上下文。
我們調用它時不需要將函數綁定到上下文。
handleButtonClick = () => {
alert("Button Clicked: " + this.state.name)
}
render() {
return (
<>
<input type="button" value="Click" onClick={this.handleButtonClick} />
</>
)
}
箭頭函數好處多多,但也有缺點。
當我們添加箭頭函數時,該函數被添加為對象實例,而不是類的原型屬性。這意味著如果我們多次復用組件,那么在組件外創建的每個對象中都會有這些函數的多個實例。
每個組件都會有這些函數的一份實例,影響了可復用性。此外因為它是對象屬性而不是原型屬性,所以這些函數在繼承鏈中不可用。
因此箭頭函數確實有其缺點。實現這些函數的最佳方法是在構造函數中綁定函數,如上所述。
10、避免使用內聯樣式屬性
使用內聯樣式時瀏覽器需要花費更多時間來處理腳本和渲染,因為它必須映射傳遞給實際 CSS 屬性的所有樣式規則。
<b style={{"backgroundColor": "blue"}}>Welcome to Sample Page</b>
在上面創建的組件中,我們將內聯樣式附加到組件。添加的內聯樣式是 JavaScript 對象而不是樣式標記。
樣式 backgroundColor 需要轉換為等效的 CSS 樣式屬性,然后才應用樣式。這樣就需要額外的腳本處理和 JS 執行工作。
更好的辦法是將 CSS 文件導入組件。
11、優化 React 中的條件渲染
安裝和卸載 React 組件是昂貴的操作。為了提升性能,我們需要減少安裝和卸載的操作。
組件 HeaderComponent 和 ContentComponent 將在位置 1 和位置 2 卸載并重新安裝。其實這是用不著的,因為這些組件沒有更改,這是一項昂貴的操作。優化方案如下:
{ this.state.name == "Mayank" && <AdminHeaderComponent></AdminHeaderComponent> }
<HeaderComponent></HeaderComponent>
<ContentComponent></ContentComponent>
在上面的代碼中,當 name 不是 Mayank 時,React 在位置 1 處放置 null。
開始 DOM diffing 時,位置 1 的元素從 AdminHeaderComponent 變為 null,但位置 2 和位置 3 的組件保持不變。
由于元素沒變,因此組件不會卸載,減少了不必要的操作。
12、不要在 render 方法中導出數據
Render 方法是 React 開發人員最熟悉的生命周期事件。
和其他生命周期事件不一樣的是,我們的核心原則是將 render() 函數作為純函數。
純函數對 render 方法意味著什么?
純函數意味著我們應該確保 setState 和查詢原生 DOM 元素等任何可以修改應用狀態的東西不會被調用。
該函數永遠不該更新應用的狀態。
更新組件狀態的問題在于,當狀態更新時會觸發另一個 render 循環,后者在內部會再觸發一個 render 循環,以此類推。
this.setState({
name: this.state.name + "_"
});
return (
<div>
<b>User Name: {this.state.name}</b>
</div>
);
在上面的代碼中,每次調用 render 函數時都會更新狀態。狀態更新后組件將立即重新渲染。因此更新狀態會導致 render 函數的遞歸調用。
render 函數應保持純凈,以確保組件以一致的方式運行和渲染。
13、為組件創建錯誤邊界
組件渲染錯誤是很常見的情況。
在這種情況下,組件錯誤不應該破壞整個應用。創建錯誤邊界可避免應用在特定組件發生錯誤時中斷。
錯誤邊界是一個 React 組件,可以捕獲子組件中的 JavaScript 錯誤。我們可以包含錯誤、記錄錯誤消息,并為 UI 組件故障提供回退機制。
錯誤邊界是基于高階組件的概念。
詳細信息參閱: https://levelup.gitconnected.com/introduction-to-reacts-higher-order-components-hocs-c42182fb634
錯誤邊界涉及一個高階組件,包含以下方法:static getDerivedStateFromError() 和 componentDidCatch()。
static 函數用于指定回退機制,并從收到的錯誤中獲取組件的新狀態。
componentDidCatch 函數用來將錯誤信息記錄到應用中。
下面是代碼示例:
import React from 'react';
export class ErrorBoundaries extends React.Component {
constructor(props) {
super(props);
this.state = {
hasErrors: false
}
}
componentDidCatch(error, info) {
console.dir("Component Did Catch Error");
}
static getDerivedStateFromError(error) {
console.dir("Get Derived State From Error");
return {
hasErrors: true
}
}
render() {
if(this.state.hasErrors === true) {
return <div>This is a Error</div>
}
return <div><ShowData name="Mayank" /></div>
}
}
export class ShowData extends React.Component {
constructor() {
super();
this.state = {
name: "Mayank"
}
}
changeData = () => {
this.setState({
name: "Anshul"
})
}
render() {
if(this.state.name === "Anshul") {
throw new Error("Sample Error")
}
return (
<div>
<b>This is the Child Component {this.state.name}</b>
<input type="button" onClick={this.changeData} value="Click To Throw Error" />
</div>
)
}
}
當 name 更新為 Anshul 時,上面的代碼會拋出錯誤。
組件 ShowData 是 ErrorBoundaries 組件內的嵌入。
因此,如果錯誤是從 ShowData 函數內拋出的,則它會被父組件捕獲,我們使用 static getDerivedStateFromError 函數和 componentDidCatch 生命周期事件中的日志數據部署回退 UI。
14、組件的不可變數據結構
React 的靈魂是函數式編程。如果我們希望組件能一致工作,則 React 組件中的狀態和 props 數據應該是不可變的。
對象的突變可能導致輸出不一致。
shouldComponentUpdate(nextProps, nextState) {
if(nextState.userInfo != this.state.userInfo) {
return true;
}
}
render() {
return (
<>
<b>User Name: {this.state.userName}
</>
)
}
如上所示。在 shouldComponentUpdate 函數中我們指定,如果 userInfo 的初始值與 userInfo 的新值不同,則應該重新渲染該組件;反之不應重新渲染組件。
15、使用唯一鍵迭代
當我們需要渲染項目列表時應該為項目添加一個鍵。
鍵可以用來識別已更改、添加或刪除的項目。鍵為元素提供了穩定的標識。一個鍵應該對應列表中的唯一一個元素。
如果開發人員沒有為元素提供鍵,則它將 index 作為默認鍵。在下面的代碼中我們默認不添加任何鍵,因此 index 將用作列表的默認鍵。
使用 index 作為鍵就不會出現標識不唯一的問題了,因為 index 只會標識所渲染的組件。
我們可以在以下場景中使用 index 作為鍵:
列表項是靜態的,項目不隨時間變化。
Items 沒有唯一 ID。
List 永遠不會重新排序或過濾。
不會從頂部或中間添加或刪除項目。
constructor() {
super();
this.state = {
inputName: "",
arrayData: ["Mayank", "Meha", "Anshul", "Arjun"]
}
}
updateUserName(event) {
this.setState({
inputName: event.target.value
})
}
addUserData() {
this.setState({
arrayData: [this.state.inputName, ...this.state.arrayData]
})
}
render() {
var dataList = this.state.arrayData.map((data, index) => {
return <div>{data}</div>;
})
return (
<div>
<input type="text" value={this.state.inputName} placeholder="Enter Unique Name" onChange={this.updateUserName.bind(this)} />
<input type="button" onClick={this.addUserData.bind(this)} value="Click To Add" /><br></br><br></br>
<b>List of Users: </b><br></br><br></br>
{dataList}<br></br>
</div>
)
}
在列表中添加項目
使用 index 作為鍵會加大錯誤率并降低應用的性能。
每當新元素添加到列表時,默認情況下 React 會同時遍歷新創建的列表和舊列表,并在需要時進行突變。
在列表頂部添加一個新元素(包含 index 作為鍵)時,全部已有組件的索引都會更新。
索引更新后,之前鍵值為 1 的元素現在的鍵值變成了 2。更新所有組件會拖累性能。
上面的代碼允許用戶在列表頂部添加新項目。但在頂部插入元素后果最嚴重。因為頂部元素一變,后面所有的元素都得跟著改鍵值,從而導致性能下降。
因此,我們應該確保鍵值和元素一一對應不會變化。
總結一下:
Key 不僅影響性能,更重要的作用是標識。隨機分配和更改的值不算是標識。
我們得知道數據的建模方式才能提供合適的鍵值。如果你沒有 ID,我建議使用某種哈希函數生成 ID。
我們在使用數組時已經有了內部鍵,但它們是數組中的索引。插入新元素時這些鍵是錯誤的。
16、事件節流和防抖
節流(throttling)和防抖(debouncing)可用來限制在指定時間內調用的事件處理程序的數量。
事件處理程序是響應不同事件(如鼠標單擊和頁面滾動)而調用的函數。事件觸發事件處理程序的速率是不一樣的。
節流的概念
節流意味著延遲函數執行。
這些函數不會立即執行,在觸發事件之前會加上幾毫秒延遲。
比如在頁面滾動時,我們不會過于頻繁地觸發滾動事件,而是將事件延遲一段時間以便將多個事件堆疊在一起。
它確保函數在特定時間段內至少調用一次。如果函數最近運行過了,它將阻止函數運行,確保函數以固定間隔定期運行。
當我們處理無限滾動并且當用戶接近頁面底部必須獲取數據時,我們可以使用節流。
否則滾動到頁面底部將觸發多個事件,并且觸發對網絡的多次調用,從而導致性能問題。
防抖的概念
防抖是指在調用停止一段時間之前忽略事件處理程序調用。
假設我們有一個事件,有一秒鐘的 debounce 時間。一旦用戶停止觸發事件,該事件的事件處理程序將在一秒鐘后觸發。
典型的例子是用戶在自動填充搜索框中鍵入數據。
一旦用戶停止鍵入,就會進行 AJAX 查詢以從 API 獲取數據。每次鍵入都進行 AJAX 調用就需要多次查詢數據庫。
因此,我們對該事件做 debounce,直到用戶不再輸入數據為止,從而減少網絡調用并提升性能。
17、使用 CDN
谷歌、亞馬遜和微軟等公司提供了許多內容分發網絡。
這些 CDN 是可在你的應用中使用的外部資源。我們甚至可以創建私有 CDN 并托管我們的文件和資源。
使用 CDN 有以下好處:
不同的域名。瀏覽器限制了單個域名的并發連接數量,具體取決于瀏覽器設置。假設允許的并發連接數為 10。如果要從單個域名中檢索 11 個資源,那么同時完成的只有 10 個,還有 1 個需要再等一會兒。CDN 托管在不同的域名 / 服務器上。因此資源文件可以分布在不同的域名中,提升了并發能力。
文件可能已被緩存。有很多網站使用這些 CDN,因此你嘗試訪問的資源很可能已在瀏覽器中緩存好了。這時應用將訪問文件的已緩存版本,從而減少腳本和文件執行的網絡調用和延遲,提升應用性能。
高容量基礎設施。這些 CDN 由大公司托管,因此可用的基礎設施非常龐大。他們的數據中心遍布全球。向 CDN 發出請求時,它們將通過最近的數據中心提供服務,從而減少延遲。這些公司會對服務器做負載平衡,以確保請求到達最近的服務器并減少網絡延遲,提升應用性能。
如果擔心安全性,可以使用私有 CDN。
18、用 CSS 動畫代替 JavaScript 動畫
在 HTML 5 和 CSS 3 出現之前,動畫曾經是 JavaScript 的專屬,但隨著 HTML 5 和 CSS 3 的引入情況開始變化。現在動畫甚至可以由 CSS 3 來處理了。
我們可以制定一些規則:
如果 CSS 可以實現某些 JS 功能,那就用 CSS。
如果 HTML 可以實現某些 JS 功能,那就用 HTML。
理由如下:
破損的 CSS 規則和樣式不會導致網頁損壞,而 JavaScript 則不然。
解析 CSS 是非常便宜的,因為它是聲明性的。我們可以為樣式并行創建內存中的表達,可以推遲樣式屬性的計算,直到元素繪制完成。
為動畫加載 JavaScript 庫的成本相對較高,消耗更多網絡帶寬和計算時間。
雖然 JavaScript 可以提供比 CSS 更多的優化,但優化過的 JavaScript 代碼也可能卡住 UI 并導致 Web 瀏覽器崩潰。
19、在 Web 服務器上啟用 gzip 壓縮
壓縮是節省網絡帶寬和加速應用的最簡單方法。
我們可以把網絡資源壓縮到更小的尺寸。Gzip 是一種能夠快速壓縮和解壓縮文件的數據壓縮算法。
它可以壓縮幾乎所有類型的文件,例如圖像、文本、JavaScript 文件、樣式文件等。Gzip 減少了網頁需要傳輸到客戶端的數據量。
當 Web 服務器收到請求時,它會提取文件數據并查找 Accept-Encoding 標頭以確定如何壓縮應用。
如果服務器支持 gzip 壓縮,資源會被壓縮后通過網絡發送。每份資源的壓縮副本(添加了 Content-Encoding 標頭)指定使用 gzip 解壓。
然后,瀏覽器將內容解壓縮原始版本在渲染給用戶。
只是 gzip 壓縮需要付出成本,因為壓縮和解壓縮文件屬于 CPU 密集型任務。但我們還是建議對網頁使用 gzip 壓縮。
20、使用 Web Workers 處理 CPU 密集任務
JavaScript 是一個單線程應用,但在渲染網頁時需要執行多個任務:
處理 UI 交互、處理響應數據、操縱 DOM 元素、啟用動畫等。所有這些任務都由單個線程處理。
可以使用 worker 來分擔主線程的負載。
Worker 線程在后臺運行,可以在不中斷主線程的情況下執行多個腳本和 JavaScript 任務。
每當需要執行長時間的 CPU 密集任務時,可以使用 worker 在單獨的線程上執行這些邏輯塊。
它們在隔離環境中執行,并且使用進程間線程通信與主線程交互。主線程就可以騰出手來處理渲染和 DOM 操作任務。
詳細信息參閱: https://medium.com/prolanceer/optimizing-react-app-performance-using-web-workers-79266afd4a7
21、React 組件的服務端渲染
服務端渲染可以減少初始頁面加載延遲。
我們可以讓網頁從服務端加載初始頁面,而不是在客戶端上渲染。這樣對 SEO 非常有利。
服務端渲染是指第一個組件顯示的內容是從服務器本身發送的,而不是在瀏覽器級別操作。之后的頁面直接從客戶端加載。
這樣我們就能把初始內容放在服務端渲染,客戶端只按需加載部分頁面。
其好處包括:
性能:初始頁面內容和數據是從服務器本身加載的,因此我們不需要添加加載器和下拉列表,而是等待初始頁面加載完畢后再加載初始組件。
SEO 優化:爬蟲在應用初始加載時查找頁面內容。在客戶端渲染時初始 Web 頁面不包含所需的組件,這些組件需要等 React 腳本等文件加載完畢后才渲染出來。
服務端渲染還可以使用第三方庫,如 Next.js。詳細信息參閱: https://nextjs.org/
這里有服務端渲染的示例項目: https://github.com/Mayankgupta688/reactServerRendering。只需從項目存儲庫執行以下步驟即可運行應用:
npm install
npm start
這個應用中“pages”文件夾里的文件是可以用服務端渲染加載的初始 URL。