Memoization技術在React中的應用

原文地址:www.linxiangjun.com/memoization…css

最近在研究React Hook優化時,發現Memoization技術被官方普遍的使用,好比useCallbackuseMemo這兩個API,分別用來返回函數的memoized版本和memoized值。Memoization其實並非什麼新技術,只是一種優化技巧,維基百科對Memoization的定義爲:html

In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.react

簡單來講Memoization就是存儲前一個值,而後每次更新時都將新傳入的值和存儲的值做比較,而後根據比較結果是否相同來返回存儲的值仍是新的值。React中使用該技術主要是爲了不沒必要要的重複渲染。git

在Redux中的應用

咱們用來優化Redux數據的reselect其實就使用了Memoization技術,這篇文章分析了reselect源碼,能夠學習reselect是若是使用該技術來優化Redux數據的。github

在React Hook中的應用

熟悉了Memoization定義後,咱們看下若是在實戰中使用這個技術。在React Hook中,useEffect方法可使用該技術來優化組件的渲染。本例子能夠在CodeSandbox中查看:smile:。redux

咱們知道useEffect的第二個數組參數做爲effect依賴存在,若是依賴數組發生改變,那麼useEffect就會從新執行。useEffect使用===來比較數組參數是否相等,使用Object.assign或者splice這類會操做時會返回新的堆來存儲引用值,就會致使值自己沒有變化卻會重複執行useEffect方法。爲了不這種狀況的發生,咱們來看下該若是來作。數組

首先,先來介紹下useRef方法,這個方法和原來的createRef很是的類似,都是建立一個可變的ref對象。在函數組件中,由於ref對象在組件的整個生命週期內保持不變,因此咱們能夠用它來存儲不會被組件re-render影響的值。因此,在下面的函數中,咱們使用ref對象來存儲useEffect中傳入的第二個數組參數。app

import React, { useRef } from "react";
import { isEqual } from "lodash";

function useDeepCompareMemoize(value) {
  const ref = useRef();

  if (!isEqual(value, ref.current)) {
    ref.current = value;
  }

  return ref.current;
}
複製代碼

能夠看到我使用了lodash中的深比較方法isEqual來對比兩個值,這個能夠根據需求自定義方法也能夠。dom

接下來編寫自定義Hook來使用useEffect函數

function useDeepCompareEffect(callback, dependencies) {
  useEffect(callback, useDeepCompareMemoize(dependencies));
}

export default useDeepCompareEffect;
複製代碼

而後使用useDeepCompareEffect代替useEffect便可。

使用的完整代碼以下:

import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import useDeepCompareEffect from "./useDeepCompareEffect.js";

import "./styles.css";

const tomCat = {
  name: "Tom",
  race: "cat"
};
const jerryMouse = {
  name: "Jerry",
  race: "mouse"
};

function App() {
  const [character, updateCharacter] = useState(tomCat);
  const effectCount = useRef(0);
  const deepCompareEffectCount = useRef(0);

  useEffect(() => {
    effectCount.current++;
  }, [character]);

  useDeepCompareEffect(() => {
    deepCompareEffectCount.current++;
  }, [character]);

  const changeStar = () => {
    const star = character.name === "Tom" ? jerryMouse : tomCat;
    updateCharacter(star);
  };

  const assignObj = () => {
    updateCharacter(Object.assign({}, character));
  };

  return (
    <div className="App"> <p>Hello, useEffect</p> <p className="star"> {character.name} {character.race} {character.friend} </p> <p>useEffect count = {effectCount.current}</p> <p>deepCompareEffectCount count = {deepCompareEffectCount.current}</p> <p> <button onClick={changeStar}>Change star</button> </p> <p> <button onClick={assignObj}>Click</button> </p> </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement); 複製代碼

一樣的,點擊Click按鈕,使用useEffect方法每次都會執行,而使用useDeepCompareEffect的只會執行一次。不過須要注意的是,該優化並非萬金油,而是要根據狀況來使用。由於深比較原本就會對性能形成必定的損耗,因此要按需使用。好比useCallbackuseMemo最好在須要重複計算時才使用,在其餘場景應用不當可能會有反作用,關於這一塊的內容推薦閱讀本文末尾中的參考文章。

參考

  1. use-deep-compare-effect
  2. memoize-one
  3. You're overusing useMemo: Rethinking Hooks memoization
相關文章
相關標籤/搜索