原文:21 Performance Optimization Techniques for React Apps
作者:Nishant
譯者:博軒
介紹
在 React
內部,React
會使用幾項巧妙的小技術,來優化計算更新 UI
時,所需要的最少的更新 DOM
的操作。在大多數情況下,即使你沒有針對性能進行專項優化,React
依然很快,但是仍有一些方法可以加速 React
應用程序。本文將介紹一些可用於改進 React
代碼的有效技巧。
1.使用不可變數據結構
數據不變性不是一種架構或者設計模式,它是一種編程思想。它會強制您考慮如何構建應用程序的數據流。在我看來,數據不變性是一種符合嚴格單項數據流的實踐。
數據不變性,這一來自函數式編程的概念,可應用於前端應用程序的設計。它會帶來很多好處,例如:
- 零副作用
- 不可變的數據對象更易於創建,測試,和使用;
- 利於解耦;
- 更加利於追蹤變化;
在 React
環境中,我們使用 Component
的概念來維護組件內部的狀態,對狀態的更改可以導致組建的重新渲染。
React
構建並在內部維護呈現的UI(Virtual DOM)。當組件的 props
或者 state
發生改變時,React
會將新返回的元素與先前呈現的元素進行比較。當兩者不相等時,React
將更新 DOM。因此,在改變狀態時,我們必須要小心。
讓我們考慮一個用戶列表組件:
state = {
users: []
}
addNewUser = () =>{
/**
* OfCourse not correct way to insert
* new user in user list
*/
const users = this.state.users;
users.push({
userName: "robin",
email: "[email protected]"
});
this.setState({users: users});
}
這裏的關注點是,我們正在將新的用戶添加到變量 users
,這裏它對應的引用是 this.state.users
。
專業提示 : 應該將 React
的狀態視爲不可變。我們不應該直接修改 this.state
,因爲 setState()
之後的調用可能會覆蓋你之前的修改。
那麼,如果我們直接修改 state
會產生什麼問題呢?比方說,我們添加 shouldComponentUpdate
,並對比 nextState
和 this.state
來確保只有當數據改變時,纔會重新渲染組件。
shouldComponentUpdate(nextProps, nextState) {
if (this.state.users !== nextState.users) {
return true;
}
return false;
}
即使用戶的數組發生了改變,React
也不會重新渲染UI了,因爲他們的引用是相同的。
避免此類問題最簡單的方法,就是避免直接修改 props
和 state
。所以,我們可以使用 concat
來重寫 addNewUser
方法:
addNewUser = () => {
this.setState(state => ({
users: state.users.concat({
timeStamp: new Date(),
userName: "robin",
email: "[email protected]"
})
}));
};
爲了處理 React
組件中 props
或者 state
的改變,我們可以考慮一下幾種處理不可變的方法:
- 對於數組:使用
[].concat
或es6的[ ...params]
- 對象:使用
Object.assign({}, ...)
或 es6的{...params}
在向代碼庫引入不變性時,這兩種方法有很長的路要走。
但是,最好使用一個提供不可變數據結構的優化庫。以下是您可以使用的一些庫:
- Immutability Helper:這是一個很好的庫,他可以在不改變源的情況下,提供修改後的數據。
- Immutable.js :這是我最喜歡的庫,因爲它提供了許多持久不可變的數據,包括:List,Stack,Map,OrderedMap,Set,OrderedSet 和 Record。
-
Seamless-immutable:一個用於提供不可變
JavaScript
數據結構的庫,他與普通的數組和對象向後兼容。 - React-copy-write:一個不可變的React狀態管理庫,帶有一個簡單的可變API,memoized選擇器和結構共享。
專業提示: React setState
方法是異步的。這意味着,setState()
方法會創建一個帶轉換的 state
, 而不是立即修改 this.state
。如果在調用setState()
方法之後去訪問 this.state
,則可能會返回現有值。爲防止這種情況,請setState
在調用完成後使用回調函數運行代碼。
其他資源:
2.函數/無狀態組件和 React.PureComponent
在 React
中,函數組件和 PureComponent
提供了兩種不同級別的組件優化方案。
函數組件防止了構造類實例,
同時函數組件可以減少整體包的大小,因爲它比類組件的的體積更小。
另一方面,爲了優化UI更新,我們可以考慮將函數組件轉換爲 PureComponent
類組件(或使用自定義 shouldComponentUpdate
方法的類)。但是,如果組件不使用狀態和其他生命週期方法,爲了達到更快的的更新,首次渲染相比函數組件會更加複雜一些。
譯註:函數組件也可以做純組件的優化:React.memo(...) 是 React v16.6 中引入的新功能。 它與 React.PureComponent 類似,它有助於控制 函數組件 的重新渲染。 React.memo(...) 對應的是函數組件,React.PureComponent 對應的是類組件。React Hooks 也提供了許多處理這種情況的方法:useCallback, useMemo。推薦兩個延伸閱讀:
A Closer Look at React Memoize Hooks: useRef, useCallback, and useMemo ,React Hooks API ⎯ 不只是 useState 或 useEffect
我們應該何時使用 React.PureComponent
?
React.PureComponent
對狀態變化進行淺層比較(shallow comparison)。這意味着它在比較時,會比較原始數據類型的值,並比較對象的引用。因此,我們必須確保在使用 React.PureComponent
時符合兩個標準:
- 組件
State
/Props
是一個不可變對象; -
State
/Props
不應該有多級嵌套對象。
專業提示: 所有使用 React.PureComponent
的子組件,也應該是純組件或函數組件。
3.生成多個塊文件
Multiple Chunk Files
您的應用程序始終以一些組件開始。您開始添加新功能和依賴項,最終您會得到一個巨大的生產文件。
您可以考慮通過利用 CommonsChunkPlugin for webpack 將供應商或第三方庫代碼與應用程序代碼分開,生成兩個單獨的文件。你最終會得到 vendor.bundle.js
和 app.bundle.js
。通過拆分文件,您的瀏覽器會緩存較少的資源,同時並行下載資源以減少等待的加載時間。
注意: 如果您使用的是最新版本的webpack,還可以考慮使用 SplitChunksPlugin
4.在 Webpack 中使用 Production
標識生產環境
如果您使用 webpack 4
作爲應用程序的模塊捆綁程序,則可以考慮將 mode
選項設置爲 production
。這基本上告訴 webpack
使用內置優化:
module.exports = {
mode: 'production'
};
或者,您可以將其作爲 CLI
參數傳遞:
webpack --mode=production
這樣做會限制對庫的優化,例如縮小或刪除開發代碼。它不會公開源代碼,文件路徑等等。
5.依賴優化
在考慮優化程序包大小的時候,檢查您的依賴項中實際有多少代碼被使用了,會很有價值。例如,如果您使用 Moment.js
會包含本地化文件的多語言支持。如果您不需要支持多種語言,那麼您可以考慮使用 moment-locales-webpack-plugin 來刪除不需要的語言環境。
另一個例子是使用 lodash
。假設你使用了 100 多種方法的 20 種,那麼你最終打包時其他額外的方法都是不需要的。因此,可以使用 lodash-webpack-plugin 來刪除未使用的函數。
以下是一些使用 Webpack
打包時可選的依賴項優化列表
6. React.Fragments
用於避免額外的 HTML 元素包裹
React.fragments
允許您在不添加額外節點的情況下對子列表進行分組。
class Comments extends React.PureComponent{
render() {
return (
<React.Fragment>
<h1>Comment Title</h1>
<p>comments</p>
<p>comment time</p>
</React.Fragment>
);
}
}
等等!我們還可以使用更加簡潔的語法代替 React.fragments
:
class Comments extends React.PureComponent{
render() {
return (
<>
<h1>Comment Title</h1>
<p>comments</p>
<p>comment time</p>
</>
);
}
}
7.避免在渲染函數中使用內聯函數定義
由於在 JavaScript
中函數就是對象({} !== {}),因此當 React
進行差異檢查時,內聯函數將始終使 prop diff
失敗。此外,如果在JSX屬性中使用箭頭函數,它將在每次渲染時創建新的函數實例。這可能會爲垃圾收集器帶來很多工作。
default class CommentList extends React.Component {
state = {
comments: [],
selectedCommentId: null
}
render(){
const { comments } = this.state;
return (
comments.map((comment)=>{
return <Comment onClick={(e)=>{
this.setState({selectedCommentId:comment.commentId})
}} comment={comment} key={comment.id}/>
})
)
}
}
您可以定義箭頭函數,而不是爲 props
定義內聯函數。
default class CommentList extends React.Component {
state = {
comments: [],
selectedCommentId: null
}
onCommentClick = (commentId)=>{
this.setState({selectedCommentId:commentId})
}
render(){
const { comments } = this.state;
return (
comments.map((comment)=>{
return <Comment onClick={this.onCommentClick}
comment={comment} key={comment.id}/>
})
)
}
}
8. JavaScript
中事件的防抖和節流
事件觸發率代表事件處理程序在給定時間內調用的次數。
通常,與滾動和鼠標懸停相比,鼠標點擊具有較低的事件觸發率。較高的事件觸發率有時會使應用程序崩潰,但可以對其進行控制。
我們來討論一些技巧。
首先,明確事件處理會帶來一些昂貴的操作。例如,執行UI更新,處理大量數據或執行計算昂貴任務的XHR請求或DOM操作。在這些情況下,防抖和節流技術可以成爲救世主,而不會對事件監聽器進行任何更改。
節流
簡而言之,節流意味着延遲功能執行。因此,不是立即執行事件處理程序/函數,而是在觸發事件時添加幾毫秒的延遲。例如,這可以在實現無限滾動時使用。您可以延遲 XHR
調用,而不是在用戶滾動時獲取下一個結果集。
另一個很好的例子是基於 Ajax
的即時搜索。您可能不希望每次按鍵時,都會請求服務器獲取新的數據,因此最好節流直到輸入字段處於休眠狀態幾毫秒之後,再請求數據。
節流可以通過多種方式實現。您可以限制觸發的事件的次數或延遲正在執行的事件來限制程序執行一些昂貴的操作。
防抖
與節流不同,防抖是一種防止事件觸發器過於頻繁觸發的技術。如果您正在使用 lodash
,則可以使用 lodash’s debounce function
來包裝你的方法。
這是一個搜索評論的演示代碼:
import debouce from 'lodash.debounce';
class SearchComments extends React.Component {
constructor(props) {
super(props);
this.state = { searchQuery: “” };
}
setSearchQuery = debounce(e => {
this.setState({ searchQuery: e.target.value });
// Fire API call or Comments manipulation on client end side
}, 1000);
render() {
return (
<div>
<h1>Search Comments</h1>
<input type="text" onChange={this.setSearchQuery} />
</div>
);
}
}
如果您不使用 lodash
,可以使用簡單版的防抖函數。
function debounce(a,b,c){var d,e;return function(){function h(){d=null,c||(e=a.apply(f,g))}var f=this,g=arguments;return clearTimeout(d),d=setTimeout(h,b),c&&!d&&(e=a.apply(f,g)),e}}
9.避免在 map
方法中使用 Index
作爲組件的 Key
在渲染列表時,您經常會看到索引被用作鍵。
{
comments.map((comment, index) => {
<Comment
{..comment}
key={index} />
})
}
但是使用 index
作爲 key
, 被用在React虛擬DOM元素
的時候,會使你的應用可能出現錯誤的數據 。當您從列表中添加或刪除元素時,如果該 key
與以前相同,則 React虛擬DOM元素
表示相同的組件。
始終建議使用唯一屬性作爲 key
,或者如果您的數據沒有任何唯一屬性,那麼您可以考慮使用shortid module
生成唯一 key
的屬性。
import shortid from "shortid";
{
comments.map((comment, index) => {
<Comment
{..comment}
key={shortid.generate()} />
})
}
但是,如果數據具有唯一屬性(例如ID),則最好使用該屬性。
{
comments.map((comment, index) => {
<Comment
{..comment}
key={comment.id} />
})
}
在某些情況下,將 index
用作 key
是完全可以的,但僅限於以下條件成立時:
- 列表和子元素是靜態的
- 列表中的子元素沒有ID,列表永遠不會被重新排序或過濾
- 列表是不可變的
10.避免使用 props
來初始化 state
(直接賦值)
我們經常需要將帶有 props
的初始數據傳遞給 React組件
來設置初始狀態值。
考慮一下這段代碼:
class EditPanelComponent extends Component {
constructor(props){
super(props);
this.state ={
isEditMode: false,
applyCoupon: props.applyCoupon
}
}
render(){
return <div>
{this.state.applyCoupon &&
<>Enter Coupon: <Input/></>}
</div>
}
}
片段中的一切看起來都不錯,對吧?
但是 props.applyCoupon
變化會發生什麼?它會映射到 state
中嘛? 如果在沒有刷新組件的情況下,props
中的值被修改了,props
中的值,將永遠不會分配給 state
中的 applyCoupon
。這是因爲構造函數僅在EditPanel
組件首次創建時被調用。
引用React文檔:
避免將 props 的值複製給 state!這是一個常見的錯誤:
constructor(props) {
super(props);
// 不要這樣做
this.state = { color: props.color };
}
如此做毫無必要(你可以直接使用 this.props.color),同時還產生了 bug(更新 prop 中的 color 時,並不會影響 state)。**只有在你刻意忽略props
更新的情況下使用。此時,應將props
重命名爲initialColor
或defaultColor
。必要時,你可以修改它的key
,以強制“重置”其內部state
。請參閱博文:你可能不需要派生 state
解決方法:
- 直接使用
props
中的屬性
class EditPanelComponent extends Component {
render(){
return <div>{this.props.applyCoupon &&
<>Enter Coupon:<Input/></>}</div>
}
}
- 當
props
改變時,您可以使用componentDidUpdate
函數來更新state
。
class EditPanelComponent extends Component {
constructor(props){
super(props);
this.state ={
applyCoupon: props.applyCoupon
}
}
// reset state if the seeded prop is updated
componentDidUpdate(prevProps){
if (prevProps.applyCoupon !== this.props.applyCoupon) {
this.setState({ applyCoupon: this.props.applyCoupon })
// or do something
}
}
render(){
return <div>{this.state.applyCoupon &&
<>Enter Coupon: <Input/></>}</div>
}
}
11.在 DOM 元素上傳遞 Props
您應該避免將屬性傳播到 DOM
元素中,因爲它會添加未知的 HTML
屬性,這是不必要的,也是一種不好的做法。
const CommentsText = props => {
return (
<div {...props}>
{props.text}
</div>
);
};
您可以設置特定屬性,而不是直接傳遞 Props:
const CommentsText = props => {
return (
<div specificAttr={props.specificAttr}>
{props.text}
</div>
);
};
12.在使用 Redux Connect 時,同時使用 Reselect 來避免組件的頻繁重新渲染
Reselect
是 Redux
的一個簡單的選擇器庫,可用於構建記憶選擇器。您可以將選擇器定義爲函數,爲 React
組件檢索 Redux
狀態片段。
const App = ({ comments, socialDetails }) => (
<div>
<CommentsContainer data={comments} />
<ShareContainer socialDetails={socialDetails} />
</div>
);
const addStaticPath = social => ({
iconPath: `../../image/${social.iconPath}`
});
App = connect(state => {
return {
comments: state.comments,
socialDetails: addStaticPath(state.social)
};
})(App);
在這段代碼中,每次評論數據在 state
中 變化時,CommentsContainer
和 ShareContainer
將會重新渲染。即使 addStaticPath
不進行任何數據更改也會發生這種情況,因爲 socialDetails
由 addStaticPath
函數返回的的數據每次都是一個新的對象 (請記住{} != {})。現在,如果我們用 Reselect
重寫 addStaticPath
,問題就會消失,因爲 Reselect
將返回最後一個函數結果,直到它傳遞新的輸入。
import { createSelector } from "reselect";
const socialPathSelector = state => state.social;
const addStaticPath = createSelector(
socialPathSelector,
social => ({
iconPath: `../../image/${social.iconPath}`
})
);
14. 記憶化的 React 組件
Memoization是一種用於優化程序速度的技術,主要通過存儲複雜函數的計算結果,當再次出現相同的輸入,直接從緩存中返回結果。memoized
函數通常更快,因爲如果使用與前一個函數相同的值調用函數,則不會執行函數邏輯,而是從緩存中獲取結果。
讓我們考慮下面簡單的無狀態UserDetails
組件。
const UserDetails = ({user, onEdit}) => {
const {title, full_name, profile_img} = user;
return (
<div className="user-detail-wrapper">
<img src={profile_img} />
<h4>{full_name}</h4>
<p>{title}</p>
</div>
)
}
在這裏,所有的孩子 UserDetails
都基於 props
。只要 props
發生變化,這個無狀態組件就會重新渲染。如果 UserDetails
組件屬性不太可能改變,那麼它是使用組件的 memoize
版本的一個很好的選擇:
const UserDetails = ({user, onEdit}) =>{
const {title, full_name, profile_img} = user;
return (
<div className="user-detail-wrapper">
<img src={profile_img} />
<h4>{full_name}</h4>
<p>{title}</p>
</div>
)
}
export default React.memo(UserDetails)
此方法將基於嚴格相等對組件的 props
和上下文進行淺層比較。
15.使用 CSS 動畫代替 JS 動畫
動畫可以提供更加流暢優秀的用戶體驗。實現動畫的方式有很多,一般來說,有三種方式可以創建動畫:
- CSS transitions
- CSS animations
- JavaScript
我們選擇哪一個取決於我們想要添加的動畫類型。
何時使用基於CSS的動畫:
- 添加 “一次性” 的轉換效果,比如切換
UI
元素的狀態。 - 較小的自身包含狀態的 UI 元素。例如,顯示氣泡提示,或者爲菜單項增加懸停效果。
何時使用基於JavaScript的動畫:
- 當你想擁有高級效果時,例如彈跳,停止,暫停,倒帶,減速或反轉;
- 當你需要對動畫進行重度控制時;
- 當你需要切換動畫時,如鼠標懸停,點擊等;
- 當使用
requestAnimationFrame
來變化視覺時。
例如,假設您希望 div
在鼠標懸停時分爲 4 個狀態設置動畫。div
在旋轉 90 度的過程中,四個階段將背景顏色從紅色變爲藍色,藍色變爲綠色,綠色變爲黃色。在這種情況下,您需要結合使用JavaScript動畫和CSS轉換來更好地控制操作和狀態更改。
16.使用CDN
CDN是一種將網站或移動應用程序中的靜態內容更快速有效地傳遞給用戶的絕佳方式。
CDN取決於用戶的地理位置。最靠近用戶的CDN服務器稱爲“邊緣服務器”。當用戶從您的網站請求通過CDN提供的內容時,他們會連接到邊緣服務器並確保最佳的在線體驗。
有一些很棒的CDN提供商。例如,CloudFront,CloudFlare,Akamai,MaxCDN,Google Cloud CDN等。
您也可以選擇Netlify或Surge.sh在CDN上託管您的靜態內容。Surge是一款免費的CLI工具,可將您的靜態項目部署到生產質量的CDN。
17.在CPU擴展任務中使用 Web Workers
Web Workers
可以在Web應用程序的後臺線程中運行腳本操作,與主執行線程分開。通過在單獨的線程中執行費力的處理,主線程(通常是UI)能夠在不被阻塞或減速的情況下運行。
在相同的執行上下文中,由於JavaScript是單線程的,我們需要並行計算。這可以通過兩種方式實現。第一個選項是使用僞並行,它基於 setTimeout
函數實現的。第二種選擇是使用 Web Workers
。
Web Workers
在執行計算擴展操作時效果最佳,因爲它在後臺的單獨線程中獨立於其他腳本執行代碼。這意味着它不會影響頁面的性能。
我們可以利用React
中的Web Workers
來執行計算昂貴的任務。
這是不使用 Web Workers
的代碼:
// Sort Service for sort post by the number of comments
function sort(posts) {
for (let index = 0, len = posts.length - 1; index < len; index++) {
for (let count = index+1; count < posts.length; count++) {
if (posts[index].commentCount > posts[count].commentCount) {
const temp = posts[index];
posts[index] = users[count];
posts[count] = temp;
}
}
}
return posts;
}
export default Posts extends React.Component{
constructor(props){
super(posts);
}
state = {
posts: this.props.posts
}
doSortingByComment = () => {
if(this.state.posts && this.state.posts.length){
const sortedPosts = sort(this.state.posts);
this.setState({
posts: sortedPosts
});
}
}
render(){
const posts = this.state.posts;
return (
<React.Fragment>
<Button onClick={this.doSortingByComment}>
Sort By Comments
</Button>
<PostList posts={posts}></PostList>
</React.Fragment>
)
}
}
當我們有 20,000
個帖子時會發生什麼?由於 sort
方法時間複雜度 O(n^2)
,它將減慢渲染速度,因爲它們在同一個線程中運行。
下面是修改後的代碼,它使用 Web Workers
來處理排序:
// sort.worker.js
// In-Place Sort function for sort post by number of comments
export default function sort() {
self.addEventListener('message', e =>{
if (!e) return;
let posts = e.data;
for (let index = 0, len = posts.length - 1; index < len; index++) {
for (let count = index+1; count < posts.length; count++) {
if (posts[index].commentCount > posts[count].commentCount) {
const temp = posts[index];
posts[index] = users[count];
posts[count] = temp;
}
}
}
postMessage(posts);
});
}
export default Posts extends React.Component{
constructor(props){
super(posts);
}
state = {
posts: this.props.posts
}
componentDidMount() {
this.worker = new Worker('sort.worker.js');
this.worker.addEventListener('message', event => {
const sortedPosts = event.data;
this.setState({
posts: sortedPosts
})
});
}
doSortingByComment = () => {
if(this.state.posts && this.state.posts.length){
this.worker.postMessage(this.state.posts);
}
}
render(){
const posts = this.state.posts;
return (
<React.Fragment>
<Button onClick={this.doSortingByComment}>
Sort By Comments
</Button>
<PostList posts={posts}></PostList>
</React.Fragment>
)
}
}
在這段代碼中,我們sort在單獨的線程中運行該方法,這將確保我們不會阻塞主線程。
您可以考慮使用 Web Workers
執行圖像處理,排序,過濾和其他消耗高昂 CPU 性能的任務。
參考: 使用Web Workers
18.虛擬化長列表
虛擬化列表或窗口化是一種在呈現長數據列表時提高性能的技術。此技術在任何時間內只展現列表的一部分,並且可以顯著減少重新渲染組件所花費的時間,以及創建 DOM 節點的總數。
有一些流行的 React
庫,如react-window
和react-virtualized
,它提供了幾個可重用的組件來展示列表,網格和表格數據。
19.分析和優化您的 Webpack
打包
在生產部署之前,您應該檢查並分析應用程序包以刪除不需要的插件或模塊。
您可以考慮使用Webpack Bundle Analyzer
,它允許您使用可視化的樹狀結構來查看 webpack
輸出文件的大小。
該模塊將幫助您:
- 瞭解你的打包內容
- 找出最大尺寸的模塊
- 找到哪些模塊有錯誤
- 優化它!
最好的優點是什麼?它支持壓縮模塊!他在解析他們以獲得模塊的真實大小,同時展示壓縮大小!下面是使用 webpack-bundle-analyzer
的示例:
20.考慮服務端渲染
服務端渲染的好處之一是爲用戶提供更好的體驗,相比客戶端渲染,用戶會更快接受到可查看的內容。
近年來,像沃爾瑪和Airbnb會使用 React
服務端渲染來爲用戶提供更好的用戶體驗。然而,在服務器上呈現擁有大數據,密集型應用程序很快就會成爲性能瓶頸。
服務器端渲染提供了性能優勢和一致的SEO表現。現在,如果您在沒有服務器端渲染的情況下檢查React應用程序頁面源,它將如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="/favicon.ico">
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script src="/app.js"></script>
</body>
</html>
瀏覽器還將獲取app.js
包含應用程序代碼的包,並在一兩秒後呈現整個頁面。
我們可以看到客戶端渲染,在到達服務器之前有兩次往返,用戶可以看到內容。現在,如果應用程序包含API驅動的數據呈現,那麼流程中會有一個暫停。
讓我們考慮用服務器端渲染來處理的同一個應用程序:
我們看到在用戶獲取內容之前,只有一次訪問服務器。那麼服務器究竟發生了什麼?當瀏覽器請求頁面時,服務器會在內存中加載React
並獲取呈現應用程序所需的數據。之後,服務器將生成的HTML
發送到瀏覽器,立即向用戶顯示內容。
以下是一些爲React應用程序提供SSR的流行解決方案:
- Next.js
- Gatsby
21.在Web服務器上啓用Gzip壓縮
Gzip
壓縮允許 Web
服務器提供更小的文件大小,這意味着您的網站加載速度更快。gzip
運行良好的原因是因爲JavaScript
,CSS
和HTML
文件使用了大量具有大量空白的重複文本。由於gzip
壓縮常見字符串,因此可以將頁面和樣式表的大小減少多達70%
,從而縮短網站的首次渲染時間。
如果您使用的是 Node / Express
後端,則可以使用 Gzipping
來解決這個問題。
const express = require('express');
const compression = require('compression');
const app = express();
// Pass `compression` as a middleware!
app.use(compression());
結論
有許多方法可以優化React
應用程序,例如延遲加載組件,使用 ServiceWorkers
緩存應用程序狀態,考慮SSR
,避免不必要的渲染等等。也就是說,在考慮優化之前,值得了解React
組件如何工作,理解 diff
算法,以及在React
中 render
的工作原理。這些都是優化應用程序時需要考慮的重要概念。
我認爲沒有測量的優化幾乎都是爲時過早的,這就是爲什麼我建議首先對性能進行基準測試和測量。您可以考慮使用 Chrome
時間線分析和可視化組件。這使您可以查看卸載,裝載,更新哪些組件以及它們相對於彼此的時間。它將幫助您開始性能優化之旅。
如果您有任何其他基於 React
的應用程序優化建議,歡迎評論區討論鴨。
本文已經聯繫原文作者,並授權翻譯,轉載請保留原文鏈接