自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何做 React 性能優(yōu)化?

開發(fā) 前端
今天帶大家來學習如何做 React 性能優(yōu)化。

大家好,我是前端西瓜哥。今天帶大家來學習如何做 React 性能優(yōu)化。

使用 React.memo()

一個組件可以通過 React.memo 方法得到一個添加了緩存功能的新組件。

const Comp = props => {  //}const MemorizedComp = React.memo(Comp);

再次渲染時,如果 props 沒有發(fā)生改變,就跳過該組件的重渲染,以實現性能優(yōu)化。

這里的 關鍵在于 props 不能改變,這也是最惡心的地方。

對于像是字符串、數值這些基本類型,對比沒有問題。但對于對象類型,就要做一些緩存工作,讓原本沒有改變的對象或函數仍舊指向同一個內存對象。

因為每次函數組件被執(zhí)行時,里面聲明的函數都是一個全新的函數,和原來的函數指向不同的內存空間,全等比較結果是 false。

處理 props 比較問題

React.memo() 最疼痛的就是處理 props 比較問題。

我們看個例子:

const MemorizedSon = React.memo(({ onClick }) => {  // ...})const Parent() {  // ...  const onClick = useCallback(() => {    // 一些復雜的判斷和邏輯  }, [a, setA, b, onSava]);  return (    <div>      <MemorizedSon onClick={onClick} />    </div>  )}

上面為了讓函數的引用不變,使用了 useCallback。函數里用到了一些變量,因為函數組件有閉包陷阱,可能會導致指向舊狀態(tài)問題,所以需要判斷這些變量是否變化,來決定是否使用緩存函數。

這里就出現了一個 連鎖反應,就是我還要給變量中的對象類型做緩存,比如這里的 setA 和 onSave 函數。然后這些函數可以又依賴其他函數,一直連鎖下去,然后你發(fā)現有些函數甚至來自其他組件,通過 props 注入。

啊我真的是麻了呀,我優(yōu)雅的 React 一下變得丑陋不堪。

怎么辦,一個方式是用 ref。ref 沒有閉包問題,且能夠在組件每次更新后保持原來的指向。

const MemorizedSon = React.memo(({ onClickRef }) => {  const onClick = onClickRef.current;})const Parent() {  // ...  const onClick = () => {    // 一些復雜的判斷和邏輯  };    const onClickRef = useRef(onClick);  onClickRef.current = onClick;    return (    <div>      <MemorizedSon onClickRef={onClickRef} />    </div>  )}

或者

const MemorizedSon = React.memo(({ onClick }) => {  // ...})const Parent() {  // ...  const onClick = useCallback(() => {    const {a, b} = propsRef.current;    const {setA, setSave} = stateRef.current;    // 一些復雜的判斷和邏輯  }, []);  return (    <div>      <MemorizedSon onClick={onClick} />    </div>  )}

當然官方也注意到這種場景,提出了 useEvent 的提案,希望能盡快實裝吧。

function Chat() {  const [text, setText] = useState('');  const onClick = useEvent(() => {    sendMessage(text);  });  return <SendButton onClick={onClick} />;}

The code inside useEvent “sees” the props/state values at the time of the call. The returned function has a stable identity even if the props/state it references change. There is no dependency array。

用了 useEvent 后,指向是穩(wěn)定的,不需要加依賴項數組。

提案詳情具體看下面這個鏈接:

https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md。

跳過中間組件

假設我們的組件嵌套是這樣的:A -> B -> C。

其中 C 需要拿到 A 的一個狀態(tài)。B 雖然不需要用到 A 的任何狀態(tài),但為了讓 C 拿到狀態(tài),所以也用 props 接收了這個,然后再傳給 C。

這樣的話,A 更新狀態(tài)時,B 也要進行不必要的重渲染。

對于這種情況,我們可以讓中間組件 B 跳過渲染:

  1. 給 B 應用 React.memo,A 的狀態(tài)不再傳給 B。
  2. A 的狀態(tài)通過發(fā)布訂閱的方式傳給 C(比如 useContext,或通過狀態(tài)管理)。

狀態(tài)下放

假設同樣還是 A -> B -> C 形式的組件嵌套。

C 需要來自 A 的狀態(tài),B 會幫忙通過 props 傳遞狀態(tài)過來。A 狀態(tài)更新時,A、B、C 都會重渲染。

如果狀態(tài)只有 C 一個組件會用到,我們可以考慮直接把狀態(tài)下放到 C。這樣當狀態(tài)更新時,就只會渲染 C。

組件提升

將組件提升到父組件的 props 上。

export default function App() {  return (    <ColorPicker>      <p>Hello, world!</p>      <ExpensiveTree />    </ColorPicker>  );}function ColorPicker({ children }) {  let [color, setColor] = useState("red");  return (    <div style={{ color }}>      <input value={color} onChange={(e) => setColor(e.target.value)} />      {children}    </div>  );}

在這里 ColorPicker 更新 color 狀態(tài)后,因為 ExpensiveTree 來自外部 props,不會改變,不會重渲染。除非是 App 中發(fā)生了狀態(tài)改變。

正確使用列表 key

進行列表渲染時,React 會要求你給它們提供 key,讓 React 識別更新后的位置變化,避免一些不必要的組件樹銷毀和重建工作。

比如你的第一個元素是 div,更新后發(fā)生了位置變化,第一個變成了 p。如果你不通過 key 告知新位置,React 就會將 div 下的整棵樹銷毀,然后構建 p 下的整棵樹,非常耗費性能。

如果你提供了位置,React 就會做真實 DOM 的位置移動,然后做樹的更新,而不是銷毀和重建。

注意狀態(tài)管理庫的觸發(fā)更新機制

對于使用 Redux 的進行狀態(tài)管理的朋友來說,我們會在函數組件中通過 useSelector 來訂閱狀態(tài)的變化,自動更新組件。

const Comp() {  const count = useSelector(state => state.count);}

useSelector 做了什么事?它會訂閱 state 的變化,當 state 變化時,會運行回調函數得到返回值,和上一次的返回值進行全等比較。如果相等,不更新組件;如果不等,更新組件。然后緩存這次的返回值。

上面這種情況還好,我們再看看寫成對象的形式。

import { shallowEqual, useSelector } from 'react-redux'const Comp() {  const { count, username } = useSelector(state => ({    count: state.count,    username: state.username  }), shallowEqual);}

上面這種寫法,因為默認用的是全等比較,所以每次 state 更新后比較結果都是 false,組件每次都更新。對于組合成的對象,你要用 shallowEqual 淺比較來替代全等比較,避免不必要的更新。

有一種情況比較特別,假設 state.userInfo 有多個屬性,username、age、acount、score、level 等。有些人會這樣寫:

const Comp() {  const { username, age } = useSelector(state => state.userInfo), shallowEqual);}

看起來沒什么問題,但里面是有陷阱的:雖然我們的組件只用到 username 和 age,但 useSelector 卻會對整個 userInfo 對象做比較。

假設我們只更新了 userInfo.level,useSelector 的比較結果就為 false 了,導致組件更新,即使你沒有用上 level,這不是我們期望的。

所以正確的寫法應該是:

const Comp() {  const { username, age } = useSelector(state => {    const { username, age } = state.userInfo;    return { username, age };  }), shallowEqual);}

使用 useSelector 監(jiān)聽狀態(tài)變化,一定要關注 state 的粒度問題。

Context 是粗粒度的

React 提供的 Context 的粒度是粗粒度的。

當 Context 的值變化時,用到該 Context 的組件就會更新。

有個問題,就是 我們提供的 Context 值通常都是一個對象,比如:

const App = () => {  return (    <EditorContext.Provider value={ visible, setVisible }>      <Editor />    </EditorContext.Provider>  );}

每當 Context 的 value 變化時,用到這個 Context 的組件都會被更新,即使你只是用這個 value 的其中一個屬性,且它沒有改變。

因為 Context 是粗粒度的。

所以你或許可以考慮在高一些層級的組件去獲取 Context,然后通過 props 分別注入到用到 Context 的不同部分的組件中。

順便一提,Context 的 value 在必要時也要做緩存,以防止組件的無意義更新。

const App = () => {  const EditorContextVal = useMemo(() => ({ visible, setVisible }), [visible, setVisible]);  return (    <EditorContext.Provider value={ visible, setVisible }>      <Editor />    </EditorContext.Provider>  );}

批量更新

有一個經典的問題是:React 的 setState 是同步還是異步的?

答案是副作用或合成事件響應函數內,是異步的,會批量執(zhí)行。其他情況(比如 setTimeout)則會同步執(zhí)行,同步的問題是會立即進行組件更新渲染,一次有多個同步 setState 就可能會有性能問題。

我們可以用ReactDOM.unstable_batchedUpdates 來將本來需要同步執(zhí)行的狀態(tài)更新變成批量的。

ReactDOM.unstable_batchedUpdates(() => {  setScore(score + 1);  setUserName('前端西瓜哥');})

不過到了 React18 后,開啟并發(fā)模式的話,就沒有同步問題了,所有的 setState 都是異步的。

Redux 的話,你可以考慮使用批量更新插件:redux-batched-actions。

import { batchActions } from 'redux-batched-actions';dispatch(batchActions([  setScoreAction(score + 1),  setUserName('前端西瓜哥')]));

redux-batched-actions 中間件確實會將多個 actions 做一個打包組合再 dispatch,你會發(fā)現 store.subscribe 的回調函數觸發(fā)次數確實變少了。

但如果你用了 react-redux 庫的話,這個庫其實在多數情況下并沒有什么用。

因為 react-redux 其實已經幫我們做了批量處理操作,同步的多個 dispatch 執(zhí)行完后,才會通知組件進行重渲染。

懶加載

有些組件,如果可以的話,可以讓組件直接不渲染,做一個懶加載。比如:

{visible && <Model />}

結尾

React 的優(yōu)化門道還是挺多的,其中的 React.memo 優(yōu)化起來確實復雜,一不小心還會整成負優(yōu)化。

所以,不要 過早進行優(yōu)化。

責任編輯:姜華 來源: 今日頭條
相關推薦

2025-03-31 01:55:00

2011-03-01 10:42:23

無線局域網局域網性能優(yōu)化

2023-12-29 08:29:15

QPS系統應用

2012-05-07 08:49:57

Clojure

2020-02-05 14:49:04

網絡性能優(yōu)化微調

2021-08-27 14:26:06

開發(fā)技能React

2022-12-07 11:21:30

Reactdiff

2022-07-25 08:02:57

Tomcat調優(yōu)組件

2019-02-25 07:07:38

技巧React 優(yōu)化

2023-11-01 17:57:56

React應用程序性能

2016-12-19 10:00:00

React性能優(yōu)化

2021-07-30 05:05:32

場景

2020-06-22 07:30:00

React開發(fā)工具

2022-08-29 08:08:58

SQLOracleCPU

2019-03-14 15:38:19

ReactJavascript前端

2020-12-18 10:40:00

ExcelJava代碼

2015-07-30 11:21:16

代碼審查

2021-04-25 09:19:22

騰訊Code Reviewleader

2022-10-19 14:16:18

樣式隔離前綴css
點贊
收藏

51CTO技術棧公眾號