面試官:說說 React 性能優化的手段有哪些?
一、是什麼
React憑藉virtual DOM和diff算法擁有高效的性能,但是某些情況下,性能明顯可以進一步提高
在前面文章中,我們瞭解到類組件通過調用setState方法, 就會導致render,父組件一旦發生render渲染,子組件一定也會執行render渲染
當我們想要更新一個子組件的時候,如下圖綠色部分:
理想狀態只調用該路徑下的組件render:
但是react的默認做法是調用所有組件的render,再對生成的虛擬DOM進行對比(黃色部分),如不變則不進行更新
從上圖可見,黃色部分diff算法對比是明顯的性能浪費的情況
二、如何做
在 React 中如何避免不必要的 render 中,我們瞭解到如何避免不必要的render來應付上面的問題,主要手段是通過shouldComponentUpdate、PureComponent、React.memo,這三種形式這裏就不再複述
除此之外, 常見性能優化常見的手段有如下:
-
避免使用內聯函數
-
使用 React Fragments 避免額外標記
-
使用 Immutable
-
懶加載組件
-
事件綁定方式
-
服務端渲染
避免使用內聯函數
如果我們使用內聯函數,則每次調用render函數時都會創建一個新的函數實例,如下:
import React from "react";
export default class InlineFunctionComponent extends React.Component {
render() {
return (
<div>
<h1>Welcome Guest</h1>
<input type="button" onClick={(e) => { this.setState({inputValue: e.target.value}) }} value="Click For Inline Function" />
</div>
)
}
}
我們應該在組件內部創建一個函數,並將事件綁定到該函數本身。這樣每次調用 render 時就不會創建單獨的函數實例,如下:
import React from "react";
export default class InlineFunctionComponent extends React.Component {
setNewStateData = (event) => {
this.setState({
inputValue: e.target.value
})
}
render() {
return (
<div>
<h1>Welcome Guest</h1>
<input type="button" onClick={this.setNewStateData} value="Click For Inline Function" />
</div>
)
}
}
使用 React Fragments 避免額外標記
用戶創建新組件時,每個組件應具有單個父標籤。父級不能有兩個標籤,所以頂部要有一個公共標籤,所以我們經常在組件頂部添加額外標籤div
這個額外標籤除了充當父標籤之外,並沒有其他作用,這時候則可以使用fragement
其不會向組件引入任何額外標記,但它可以作爲父級標籤的作用,如下所示:
export default class NestedRoutingComponent extends React.Component {
render() {
return (
<>
<h1>This is the Header Component</h1>
<h2>Welcome To Demo Page</h2>
</>
)
}
}
事件綁定方式
在事件綁定方式中,我們瞭解到四種事件綁定的方式
從性能方面考慮,在render方法中使用bind和render方法中使用箭頭函數這兩種形式在每次組件render的時候都會生成新的方法實例,性能欠缺
而constructor中bind事件與定義階段使用箭頭函數綁定這兩種形式只會生成一個方法實例,性能方面會有所改善
使用 Immutable
在理解 Immutable 中,我們瞭解到使用 Immutable可以給 React 應用帶來性能的優化,主要體現在減少渲染的次數
在做react性能優化的時候,爲了避免重複渲染,我們會在shouldComponentUpdate()中做對比,當返回true執行render方法
Immutable通過is方法則可以完成對比,而無需像一樣通過深度比較的方式比較
懶加載組件
從工程方面考慮,webpack存在代碼拆分能力,可以爲應用創建多個包,並在運行時動態加載,減少初始包的大小
而在react中使用到了Suspense和 lazy組件實現代碼拆分功能,基本使用如下:
const johanComponent = React.lazy(() => import(/* webpackChunkName: "johanComponent" */ './myAwesome.component'));
export const johanAsyncComponent = props => (
<React.Suspense fallback={<Spinner />}>
<johanComponent {...props} />
</React.Suspense>
);
服務端渲染
採用服務端渲染端方式,可以使用戶更快的看到渲染完成的頁面
服務端渲染,需要起一個node服務,可以使用express、koa等,調用react的renderToString方法,將根組件渲染成字符串,再輸出到響應中
例如:
import { renderToString } from "react-dom/server";
import MyPage from "./MyPage";
app.get("/", (req, res) => {
res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
res.write("<div id='content'>");
res.write(renderToString(<MyPage/>));
res.write("</div></body></html>");
res.end();
});
客戶端使用 render 方法來生成 HTML
import ReactDOM from 'react-dom';
import MyPage from "./MyPage";
ReactDOM.render(<MyPage />, document.getElementById('app'));
其他
除此之外,還存在的優化手段有組件拆分、合理使用hooks等性能優化手段...
三、總結
通過上面初步學習,我們瞭解到react常見的性能優化可以分成三個層面:
-
代碼層面
-
工程層面
-
框架機制層面
通過這三個層面的優化結合,能夠使基於react項目的性能更上一層樓
參考文獻
-
https://zhuanlan.zhihu.com/p/108666350
-
https://segmentfault.com/a/1190000007811296
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/e_SJ0YXCAbZMAUKz4dy78A