[小組分享] React 當中性能優化手段整理

內部小組分享底稿.

回顧一下 React

  • class 組件的優化
  • useMemo 提供的優化
  • React.memo 優化
  • useCallback 優化
  • 避免 render 當中的 DOM 操作

class 組件的優化

通過判斷減少數據變化觸發的重新渲染, 以及之後的 DOM diff

shouldComponentUpdate(nextProps, nextState) {
  if (this.props.color !== nextProps.color) {
    return true;
  }
  if (this.state.count !== nextState.count) {
    return true;
  }
  return false;
}

JavaScript 對象引用問題

函數式語言當中, 語言設計允許兩個對象一樣, 舉例 Clojure:

(= {:a 1} {:a 1}) ; true

(identical? {:a 1} {:a 1}) ; false

遞歸匹配, 性能並不高.

JavaScript 對象基於引用傳值, 比較單一

{a: 1} === {a: 1} // false

大體方案, 通過手動維護, 讓相同的數據儘量保證引用一致, 控制性能.

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

useMemo 優化

每個函數體當中生成的對象都會有新的引用, useMemo 可以保留一致的引用.

const myObject = useMemo(() => ({ key: "value" }), [])

注意: 用花括號直接寫對象基本上就是新的引用了,

{}
{a: 1}

{...obj}

一般組件內部不變的對象, 都是從 state, ref, 再或者組件外全局有一個引用.


React.memo 優化

判斷參數是否改變, 如果沒有改變, 就直接複用已有的組件, 不重新生成:

const MyComponent = React.memo(function MyComponent(props) {
  /* only rerenders if props change */
});

React.memo 有第二個參數, 用於自定義判斷的規則:

const MemoItem = React.memo(Item, (prevProps, nextProps) => {
  if (prevProps.item.selected === nextProps.item.selected) {
    return true;
  }
  return false;
});

useCallback 優化

使用 React.memo 包裹組件:

let Inner: FC<{
  onClick: () => void
}> = React.memo((props) => {
  return <div>
    <span>inner</span>
    </div>;
});

使用 useCallback

let Outer: FC<{}> = React.memo((props) => {
  const [counter, setCounter] = useState(0);
  const onClick = useCallback(()=>{ 
    setCounter(prevState => ++prevState)
  },[]);
  return <div>
    <span>outer: {counter}</span>
    <Inner onClick={onClick} />
  </div>;
});

避免 render 當中的 DOM 操作

let NewComponent: FC<{}> = React.memo((props) => {

  let elRef = useRef<HTMLDivElement>()

  // 錯誤寫法
  if (elRef.current) {
    elRef.current.style.color = 'red'
  }

  return <div ref={elRef}></div>;
});

DOM 發生改變的時候, 一般會有比較多後續的佈局和 compose 計算去繪製新的界面.

特別是在腳本執行過程當中發生的話, 會對性能有明顯影響.

腳本執行完再執行, 讓瀏覽器自動處理(合併, 避免頻繁 DOM 操作).


業務相關

  • immer 對優化方案的影響
  • Rex 組件當中優化的坑
  • 路由相關的優化
  • 性能調試

Immer 對優化方案的影響

let a = {}
let b = produce(a, draft => {
  draft.b = 1
})

a === b // false

如果數據不發生改變, 直接用原始數據.

(Hooks API 之後, 數據被拆散了, 可以減少 immer 的使用.)


Rex 當中優化的相關

class 組件, 高階組件當中自動做了基礎的優化.

shouldComponentUpdate(nextProps: IRexDataLayerProps, nextState: any) {
  if (!shallowequal(nextProps.parentProps, this.props.parentProps)) return true;
  if (!shallowequal(nextProps.computedProps, this.props.computedProps)) return true;
  return false;
}

Hook API, 沒有中間一層組件, 直接觸發當前組件更新, 存在性能問題.(還要考慮優化方案)

let contextData = useRexContext((store: IGlobalStore) => {
  return {
    data: store.data,
    homeData: store.homeData,
  };
});

業務當中一般可以接受, 因爲數據通常都是在更新的. 新能敏感場景需要額外考慮.


ruled-router 提供的優化

/home/plant/123/shop/456/789

解析爲

{
  "raw": "home",
  "name": "home",
  "matches": true,
  "restPath": ["plant", "123", "shop", "456", "789"],
  "params": {},
  "data": {},
  "next": {
    "raw": "plant/:plantId",
    "name": "plant",
    "matches": true,
    "restPath": ["shop", "456", "789"],
    "params": {
      "plantId": "123"
    },
    "data": {
      "plantId": "123"
    },
    "next": {
      "raw": "shop/:shopId/:corner",
      "name": "shop",
      "matches": true,
      "next": null,
      "restPath": [],
      "data": {
        "shopId": "456",
        "corner": "789"
      },
      "params": {
        "plantId": "123",
        "shopId": "456",
        "corner": "789"
      }
    }
  }
}

生成對象保存起來, 路由發生變更時再重新解析. 這樣對象引用一般保持一致.


性能優調試

DevTools

https://developers.google.com...

React DevTools

https://www.debugbear.com/blo...


其他

官方推薦性能優化方案...

https://reactjs.org/docs/opti...


實際遇到

樹形組件: 隱藏子樹, 定製減少更新. (個人建議看情況自己實現, 通用組件一般都不好優化).

useMemo

Dropdown 的替換, 老版本 antd 的 bug(升級 [email protected]).

https://github.com/react-comp...


需要優化

  • form
  • table
  • ...

THX. QA.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章