日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

React 应用的性能优化思路

發布時間:2025/3/21 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 React 应用的性能优化思路 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

要點梗概

React 應用主要的性能問題在于多余的處理和組件的 DOM 比對。為了避免這些性能陷阱,你應該盡可能的在 shouldComponentUpdate 中返回 false

簡而言之,歸結于如下兩點:

  • 加速shouldComponentUpdate 的檢查

  • 簡化shouldComponentUpdate 的檢查

  • 免責聲明!

    文章中的示例是用 React + Redux 寫的。如果你用的是其它的數據流庫,原理是相通的但是實現會不同。

    在文章中我沒有使用 immutability (不可變)庫,只是一些普通的 es6 和一點 es7。有些東西用不可變數據庫要簡單一點,但是我不準備在這里討論這一部分內容。

    React 應用的主要性能問題是什么?

  • 組件中那些不更新 DOM 的冗余操作

  • DOM 比對那些無須更新的葉子節點

    • 雖則 DOM 比對很出色并加速了 React ,但計算成本是不容忽視的
  • React 默認的渲染行為是怎樣的?

    我們來看一下 React 是如何渲染組件的。

    初始化渲染

    在初始化渲染時,我們需要渲染整個應用

    (綠色 = 已渲染節點)

    每一個節點都被渲染 —— 這很贊!現在我們的應用呈現了我們的初始狀態。

    提出改變

    我們想更新一部分數據。這些改變只和一個葉子節點相關

    理想更新

    我們只想渲染通向葉子節點的關鍵路徑上的這幾個節點

    默認行為

    如果你不告訴 React 別這樣做,它便會如此

    (橘黃色 = 浪費的渲染)

    哦,不!我們所有的節點都被重新渲染了。

    React 的每一個組件都有一個 shouldComponentUpdate(nextProps, nextState) 函數。它的職責是當組件需要更新時返回 true , 而組件不必更新時則返回 false 。返回 false 會導致組件的 render 函數不被調用。React 總是默認在 shouldComponentUpdate 中返回 true,即便你沒有顯示地定義一個 shouldComponentUpdate 函數。

    // 默認行為shouldComponentUpdate(nextProps, nextState) {return true;}

    這就意味著在默認情況下,你每次更新你的頂層級的 props,整個應用的每一個組件都會渲染。這是一個主要的性能問題。

    我們如何獲得理想的更新?

    盡可能的在 shouldComponentUpdate 中返回 false

    簡而言之:

  • 加速shouldComponentUpdate 的檢查

  • 簡化shouldComponentUpdate 的檢查

  • 加速 shouldComponentUpdate 檢查

    理想情況下我們不希望在 shouldComponentUpdate 中做深等檢查,因為這非常昂貴,尤其是在大規模和擁有大的數據結構的時候。

    class Item extends React.component {shouldComponentUpdate(nextProps) {// 這很昂貴return isDeepEqual(this.props, nextProps);}// ...}

    一個替代方法是只要對象的值發生了變化,就改變對象的引用。

    const newValue = {...oldValue// 在這里做你想要的修改};// 快速檢查 —— 只要檢查引用newValue === oldValue; // false// 如果你愿意也可以用 Object.assign 語法const newValue2 = Object.assign({}, oldValue);newValue2 === oldValue; // false

    在 Redux reducer 中使用這個技巧:

    // 在這個 Redux reducer 中,我們將改變一個 item 的 descriptionexport default (state, action) {if(action.type === 'ITEM_DESCRIPTION_UPDATE') {const { itemId, description } = action;const items = state.items.map(item => {// action 和這個 item 無關 —— 我們可以不作修改直接返回這個 itemif(item.id !== itemId) {return item;}// 我們想改變這個 item// 這會保留原本 item 的值,但// 會返回一個更新過 description 的新對象return {...item,description};});return {...state,items};}return state;}

    如果你采用這個方法,那你只需在 shouldComponentUpdate 函數中作引用檢查

    // 超級快 —— 你所做的只是檢查引用!shouldComponentUpdate(nextProps) {return isObjectEqual(this.props, nextProps);}

    isObjectEqual 的一個實現示例

    const isObjectEqual = (obj1, obj2) => {if(!isObject(obj1) || !isObject(obj2)) {return false;}// 引用是否相同if(obj1 === obj2) {return true;}// 它們包含的鍵名是否一致?const item1Keys = Object.keys(obj1).sort();const item2Keys = Object.keys(obj2).sort();if(!isArrayEqual(item1Keys, item2Keys)) {return false;}// 屬性所對應的每一個對象是否具有相同的引用?return item2Keys.every(key => {const value = obj1[key];const nextValue = obj2[key];if(value === nextValue) {return true;}// 數組例外,再檢查一個層級的深度return Array.isArray(value) && Array.isArray(nextValue) && isArrayEqual(value, nextValue);});};const isArrayEqual = (array1 = [], array2 = []) => {if(array1 === array2) {return true;}// 檢查一個層級深度return array1.length === array2.length &&array1.every((item, index) => item === array2[index]);};

    簡化 shouldComponentUpdate 檢查

    先看一個復雜的 shouldComponentUpdate 示例

    // 關注分離的數據結構(標準化數據)const state = {items: [{id: 5,description: 'some really cool item'}]// 表示用戶與系統交互的對象interaction: {selectedId: 5}};

    如果這樣組織你的數據,會使得在 shouldComponentUpdate 中進行檢查變得困難

    import React, { Component, PropTypes } from 'react'class List extends Component {propTypes = {items: PropTypes.array.isRequired,iteraction: PropTypes.object.isRequired}shouldComponentUpdate (nextProps) {// items 中的元素是否發生了改變?if(!isArrayEqual(this.props.items, nextProps.items)) {return true;}// 從這里開始事情會變的很恐怖// 如果 interaction 沒有變化,那可以返回 false (真棒!)if(isObjectEqual(this.props.interaction, nextProps.interaction)) {return false;}// 如果代碼運行到這里,我們知道:// 1. items 沒有變化// 2. interaction 變了// 我們需要 interaction 的變化是否與我們相干const wasItemSelected = this.props.items.any(item => {return item.id === this.props.interaction.selectedId})const isItemSelected = nextProps.items.any(item => {return item.id === nextProps.interaction.selectedId})// 如果發生了改變就返回 true// 如果沒有發生變化就返回 falsereturn wasItemSelected !== isItemSelected;}render() {<div>{this.props.items.map(item => {const isSelected = this.props.interaction.selectedId === item.id;return (<Item item={item} isSelected={isSelected} />);})}</div>}}

    問題1:shouldComponentUpdate 體積龐大

    你可以看出一個非常簡單的數據對應的 shouldComponentUpdate 即龐大又復雜。這是因為它需要知道數據的結構以及它們之間的關聯。shouldComponentUpdate 函數的復雜度和體積只隨著你的數據結構增長。這很容易導致兩點錯誤:

  • 在不應該返回 false 的時候返回 false(應用顯示錯誤的狀態)

  • 在不應該返回 true 的時候返回 true(引發性能問題)

  • 為什么要讓事情變得這么復雜?你只想讓這些檢查變得簡單一點,以至于你根本就不必考慮它們。

    問題2:父子級之間強耦合

    通常而言,應用都要推廣松耦合(組件對其它的組件知道的越少越好)。父組件應該盡量避免知曉其子組件的工作原理。這就允許你改變子組件的行為而無須讓父級知曉這些變化(假設 PropsTypes 保持不變)。它還允許子組件獨立運轉,而不必讓父級緊密的控制其行為。

    解決辦法:壓平你的數據

    通過壓平(合并)你的數據結構,你可以重新使用非常簡單的引用檢查來看是否有什么發生了變化。

    const state = {items: [{id: 5,description: 'some really cool item',// interaction 現在存在于 item 的內部interaction: {isSelected: true}}}};

    這樣組織你的數據使得在 shouldComponentUpdate 中做檢查變得簡單

    import React, {Component, PropTypes} from 'react'class List extends Component {propTypes = {items: PropTypes.array.isRequired}shouldComponentUpdate(nextProps) {// so easy,麻麻再也不用擔心我的更新檢查了return isObjectEqual(this.props, nextProps);}render() {<div>{this.props.items.map(item => {return (<Item item={item}isSelected={item.interaction.isSelected} />)})}</div>}}

    如果你想要更新 interaction 你就改變整個對象的引用

    // redux reducerexport default (state, action) => {if(action.type === 'ITEM_SELECT') {const { itemId } = action;const items = state.items.map(item => {if(item.id !== itemId) {return item;}// 改變整個對象的引用return {...item,interaction: {isSelected: true}}})return {...state,items};}return state;};

    誤區:引用檢查與動態 props

    一個創建動態 props 的例子

    class Foo extends React.Component {render() {const {items} = this.props;// 這個對象每次都有一個新的引用const newData = { hello: 'world' };return <Item name={name} data={newData} />}}class Item extends React.Component {// 即便前后兩個對象的值相同,檢查也總會返回true,因為 `data` 每次都會得到一個新的引用shouldComponentUpdate(nextProps) {return isObjectEqual(this.props, nextProps);}}

    通常我們不會在組件中創建一個新的 props 把它傳下來 。但是,這在循環中更為常見

    class List exntends React.Component {render() {const {items} = this.props;<div>{items.map((item, index) => {// 這個對象每次都會獲得一個新引用const newData = {hello: 'world',isFirst: index === 0};return <Item name={name} data={newData} />})}</div>}}

    這在創建函數時很常見

    import myActionCreator from './my-action-creator';class List extends React.Component {render() {const {items, dispatch} = this.props;<div>{items.map(item => {// 這個函數的引用每次都會變const callback = () => {dispatch(myActionCreator(item));}return <Item name={name} onUpdate={callback} />})}</div>}}

    解決問題的策略

  • 避免在組件中創建動態的 props
  • 改善你的數據模型,這樣你就可以直接把 props 傳下來

  • 把動態 props 轉化成滿足全等(===)的類型傳下來
  • eg:

    • boolean

    • number

    • string

    const bool1 = true;const bool2 = true;bool1 === bool2; // trueconst string1 = 'hello';const string2 = 'hello';string1 === string2; // true

    如果你實在需要傳遞動態對象,那就把它當作字符串傳下來,再在子級進行解構

    render() {const {items} = this.props;<div>{items.map(item => {// 每次獲得新引用const bad = {id: item.id,type: item.type};// 相同的值可以滿足嚴格的全等 '==='const good = `${item.id}::${item.type}`;return <Item identifier={good} />})}</div>}

    特殊情況:函數

  • 如果可以的話,盡量避免傳遞函數。相反,讓子組件自由的 dispatch 動作。這還有個附加的好處就是把業務邏輯移出組件。

  • shouldComponetUpdate 中忽略函數檢查。這樣不是很理想,因我們不知道函數的值是否變化了。

  • 創建一個 data -> function 的不可變綁定。你可以在 componentWillReceiveProps 函數中把它們存到 state 中去。這樣就不會在每一次 render 時拿到新的引用。這個方法極度笨重,因為你須要維護和更新一個函數列表。

  • 創建一個擁有正確 this 綁定的中間組件。這也不夠理想,因為你在層級中引入了一個冗余層。

  • 任何其它你能夠想到的、能夠避免每次 render 調用時創建一個新函數的方法。

  • 方案4 的示例

    // 引入另外一層 'ListItem'<List><ListItem> // 你可以在這里創建正確的 this 綁定<Item /></ListItem></List>class ListItem extends React.Component {// 這樣總能得到正確的 this 綁定,因為它綁定在了實例上// 感謝 es7!const callback = () => {dispatch(doSomething());}render() {return <Item callback={this.callback} item={this.props.item} />}}

    工具

    以上列出來的所有規則和技巧都是通過使用性能測量工具發現的。使用工具可以幫助你發現你的應用的具體性能問題所在。

    console.time

    這一個相當簡單:

  • 開始一個計時器

  • 做點什么

  • 停止計時器

  • 一個比較好的做法是使用 Redux 中間件:

    export default store => next => action => {console.time(action.type)// `next` 是一個函數,它接收 'action' 并把它發送到 ‘reducers' 進行處理// 這會導致你應有的一次重渲const result = next(action);// 渲染用了多久?console.timeEnd(action.type);return result;};

    用這個方法可以記錄你應用的每一個 action 和它引起的渲染所花費的時間。你可以快速知道哪些 action 渲染時間最長,這樣當你解決性能問題時就可以從那里著手。拿到時間值還能幫助你判斷你所做的性能優化是否奏效了。

    React.perf

    這個工具的思路和 console.time 是一致的,只不過用的是 React 的性能工具:

  • Perf.start()

  • do stuff

  • Perf.stop()

  • Redux 中間件示例:

    import Perf from 'react-addons-perf';export default store => next => action => {const key = `performance:${action.type}`;Perf.start();// 拿到新的 state 重渲應用const result = next(action);Perf.stop();console.group(key);console.info('wasted');Perf.printWasted();// 你可以在這里打印任何你感興趣的 Perf 測量值console.groupEnd(key);return result;};

    console.time 方法類似,它能讓你看到你每一個 action 的性能指標。更多關于 React 性能 addon 的信息請點擊這里

    瀏覽器工具

    CPU 分析器火焰圖表在尋找你的應用程序的性能問題時也能發揮作用。

    在做性能分析時,火焰圖表會展示出每一毫秒你的代碼的 Javascript 堆棧的狀態。在記錄的時候,你就可以確切地知道任意時間點執行的是哪一個函數,它執行了多久,又是誰調用了它。—— Mozilla

    Firefox: 點擊查看

    Chrome: 點擊查看

    感謝閱讀,祝你順利構建出高性能的 React 應用!

    總結

    以上是生活随笔為你收集整理的React 应用的性能优化思路的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。