memo、useCallback、useMemo 傻傻分不清

前言

memo、useCallback、useMemo 是否是不少時候都傻傻分不清楚?react

看完這篇文章,快速理解它們各自的使用場景,以一個實際開發場景爲例:在使用 React 進行開發的時候,優化渲染是一項常備技能。最多見的是父組件渲染的時候,連帶的子組件也會從新執行。緩存

例如:markdown

// in App.js
import React, { useState, useMemo, memo } from "react";
import ChildComponent from "./ChildComponent";

export default function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };
  
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>{count}</h2>
      <button onClick={handleClick}>click</button>
      <ChildComponent />
    </div>
  );

// in ChildComponent.js
const ChildComponent = (props) => {
  console.log("ChildComponent Running");
  return <div>{`這裏是 ChildComponent`}</div>;
};

export default ChildComponent;
複製代碼

每次點擊後,父元素中的 state 都會發生變化,可是子元素並不依賴於父元素的 state,可是子元素一樣會從新執行一遍。函數

React.PureComponent

若是是使用 class 來實現的組件,可使用 React.PureComponent 來定義組件。優化

class Greeting extends React.PureComponent {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
複製代碼

React.PureComponent 與 React.Component 區別在於:PureComponent 實現了 shouldComponentUpdate 方法,能夠對 props 和 state 進行淺比較,從而優化組件渲染。this

React.Memo

使用 React.memo 將組件包裹。React.memo 包裹後的組件將在 props 不變的狀況下,省略組件渲染的操做直接複用最近一次的渲染結果。React.memo 默認對複雜對象作淺層比較,也能夠自定義比較函數做爲第二個參數傳遞給 React.memo。spa

const ChildComponentMemo = memo(ChildComponent, (prevProps, nextProps) => {
	// return true or false;
});
複製代碼

useCallback

可是 React.memo 有缺陷。若是傳入的是一個引用數據類型,那麼在修改與子組件無關的 state 時, ChildComponet 依然會從新執行。3d

// in ChildComponent.js
const ChildComponent = (props) => {
  console.log("ChildComponent Running");
  return (
    <div>
      {`這裏是 ChildComponent`}
      <button onClick={props.handleAddCat}>add cat</button>
    </div>
  );
};

// in APP.js
export default function App() {
  const [dogCount, setDogCount] = useState(0);
  const [catCount, setCatCount] = useState(0);

  const handleAddDog = () => {
    setDogCount(dogCount + 1);
  };

  // 這個函數要傳給子組件
  const handleAddCat = () => {
    setCatCount(catCount + 1);
  };

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>dog:{dogCount}</h2>
      <h2>cat:{catCount}</h2>
      <button onClick={handleAddDog}>click</button>
      <ChildComponentMemo handleAddCat={handleAddCat}/>
    </div>
  );
}
複製代碼

這是由於 click 後 state 發生變化,那麼父組件(App.js)會從新執行,致使引用類型會被從新建立(引用地址發生改變),子組件(ChildComponent.js)會認爲 props 發生改變,從而從新執行。code

這時候須要使用 useCallback 來爲引用類型地址作一個緩存:orm

// in APP.js
export default function App() {
  const [dogCount, setDogCount] = useState(0);
  const [catCount, setCatCount] = useState(0);

  const handleAddDog = () => {
    setDogCount(dogCount + 1);
  };

  // dogCount 改變後,handleAddCat 的地址不會發生改變
  // ChildComponent 也不會從新執行
  const handleAddCat = useCallback(() => {
    setCatCount(catCount + 1);
  }, [catCount]);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>dog:{dogCount}</h2>
      <h2>cat:{catCount}</h2>
      <button onClick={handleAddDog}>click</button>
      <ChildComponentMemo handleAddCat={handleAddCat}/>
    </div>
  );
}
複製代碼

useMemo

不少人會問 useMemo 和 useCallback 有什麼區別?

一句話總結:useCallback 緩存的是函數,useMemo 緩存的是函數執行後的值。

即 useCallback(fn, [deps]) 等價於 useMemo(() => fn, [deps])

// in App.js
function App() {
  const [dogCount, setDogCount] = useState(0);
  const [catCount, setCatCount] = useState(0);
  
  const handleAddDog = () => {
    setDogCount(dogCount + 1);
  };
  
	const computeValue = (catCount) => {
    // 假設這裏有大量計算
    console.log('computeValue Running');
    
    return catCount + 1;
  }

  // 當 dogCount 發生變化,這裏會從新執行 computeValue 函數
  const value = computeValue(catCount);
  console.log('value: ', value);
}
複製代碼

當點擊 click 增長 dogCount 數量時,computeCount 依然會執行,可是它的計算與 dogCount 毫無關係,其執行的結果是同樣的。

這時候能夠用 useMemo 將計算值緩存起來:

// in App.js
function App() {
  const [dogCount, setDogCount] = useState(0);
  const [catCount, setCatCount] = useState(0);
  
  const handleAddDog = () => {
    setDogCount(dogCount + 1);
  };
  
	const computeValue = (catCount) => {
    // 假設這裏有大量計算
    console.log('computeValue Running');
    
    return catCount + 1;
  }

  // 使用 useMemo 後,只有當 catCount 變化時,纔會從新計算獲得 value
  // 若是 deps 爲空,那麼只有在第一次渲染時執行
  const value = useMemo(() => computeValue(catCount), [catCount])
}
複製代碼
相關文章
相關標籤/搜索