1. 程式人生 > 其它 >react中 useMemo與useCallback使用

react中 useMemo與useCallback使用

記錄學習的文章,參考

回顧

在介紹一下這兩個hooks的作用之前,我們先來回顧一下react中的效能優化。在hooks誕生之前,如果元件包含內部state,我們都是基於class的形式來建立元件。當時我們也知道,react中,效能的優化點在於:

1、呼叫setState,就會觸發元件的重新渲染,無論前後的state是否不同
2、父元件更新,子元件也會自動的更新
基於上面的兩點,我們通常的解決方案是:使用immutable進行比較,在不相等的時候呼叫setState;在shouldComponentUpdate中判斷前後的props和state,如果沒有變化,則返回false來阻止更新。 在hooks出來之後,我們能夠使用function的形式來建立包含內部state的元件。但是,使用function的形式,失去了上面的shouldComponentUpdate,我們無法通過判斷前後狀態來決定是否更新。
而且,在函式元件中,react不再區分mount和update兩個狀態,這意味著函式元件的每一次呼叫都會執行其內部的所有邏輯,那麼會帶來較大的效能損耗。因此useMemo 和useCallback就是解決效能問題的殺手鐗。

對比

我們先簡單的看一下useMemo和useCallback的呼叫簽名:

function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T; function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;
useCallback和useMemo的引數跟useEffect一致,他們之間最大的區別有是useEffect會用於處理副作用,而前兩個hooks不能。

useMemo和useCallback都會在元件第一次渲染的時候執行,之後會在其依賴的變數發生改變時再次執行;並且這兩個hooks都返回快取的值,useMemo返回快取的變數,useCallback返回快取的函式。

useMemo

我們來看一個反例:

import React from 'react';
 
 
export default function WithoutMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    function expensive() {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum +
= i; } return sum; } return <div> <h4>{count}-{val}-{expensive()}</h4> <div> <button onClick={() => setCount(count + 1)}>+c1</button> <input value={val} onChange={event => setValue(event.target.value)}/> </div> </div>; }

這裡建立了兩個state,然後通過expensive函式,執行一次昂貴的計算,拿到count對應的某個值。我們可以看到:無論是修改count還是val,由於元件的重新渲染,都會觸發expensive的執行(能夠在控制檯看到,即使修改val,也會列印);但是這裡的昂貴計算只依賴於count的值,在val修改的時候,是沒有必要再次計算的。在這種情況下,我們就可以使用useMemo,只在count的值修改時,執行expensive計算:

export default function WithMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
    const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }, [count]);
 
    return <div>
        <h4>{count}-{expensive}</h4>
        {val}
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

上面我們可以看到,使用useMemo來執行昂貴的計算,然後將計算值返回,並且將count作為依賴值傳遞進去。這樣,就只會在count改變的時候觸發expensive執行,在修改val的時候,返回上一次快取的值。

useCallback

講完了useMemo,接下來是useCallback。useCallback跟useMemo比較類似,但它返回的是快取的函式。我們看一下最簡單的用法:

const fnA = useCallback(fnB, [a])
上面的useCallback會將我們傳遞給它的函式fnB返回,並且將這個結果快取;當依賴a變更時,會返回新的函式。既然返回的是函式,我們無法很好的判斷返回的函式是否變更,所以我們可以藉助ES6新增的資料型別Set來判斷,具體如下:
import React, { useState, useCallback } from 'react';
 
const set = new Set();
 
export default function Callback() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        console.log(count);
    }, [count]);
    set.add(callback);
 
 
    return <div>
        <h4>{count}</h4>
        <h4>{set.size}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}

我們可以看到,每次修改count,set.size就會+1,這說明useCallback依賴變數count,count變更時會返回新的函式;而val變更時,set.size不會變,說明返回的是快取的舊版本函式。

知道useCallback有什麼樣的特點,那有什麼作用呢?

使用場景是:有一個父元件,其中包含子元件,子元件接收一個函式作為props;通常而言,如果父元件更新了,子元件也會執行更新;但是大多數場景下,更新是沒有必要的,我們可以藉助useCallback來返回函式,然後把這個函式作為props傳遞給子元件;這樣,子元件就能避免不必要的更新。

import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        return count;
    }, [count]);
    return <div>
        <h4>{count}</h4>
        <Child callback={callback}/>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}
 
function Child({ callback }) {
    const [count, setCount] = useState(() => callback());
    useEffect(() => {
        setCount(callback());
    }, [callback]);
    return <div>
        {count}
    </div>
}

不僅是上面的例子,所有依賴本地狀態或props來建立函式,需要使用到快取函式的地方,都是useCallback的應用場景。

多談一點

useEffect、useMemo、useCallback都是自帶閉包的。也就是說,每一次元件的渲染,其都會捕獲當前元件函式上下文中的狀態(state, props),所以每一次這三種hooks的執行,反映的也都是當前的狀態,你無法使用它們來捕獲上一次的狀態。對於這種情況,我們應該使用ref來訪問。

原始碼:

https://github.com/anymost/hooks-demo/blob/master/src/page/Memo.js​github.com

https://github.com/anymost/hooks-demo/blob/master/src/page/Callback.js​github.com

原文https://zhuanlan.zhihu.com/p/66166173

參考

https://blog.csdn.net/sinat_17775997/article/details/94453167