前言
閱讀本文章需要對 React hooks 中 useState 和 useEffect 有基礎的瞭解。我的這篇文章內有大致介紹 在 React 專案中全量使用 Hooks。
useCallback
useCallback 的作用
官方文件:
Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
簡單來說就是返回一個函式,只有在依賴項發生變化的時候才會更新(返回一個新的函式)。
useCallback 的應用
線上程式碼: Code Sandbox
import React, { useState, useCallback } from 'react';
import Button from './Button';
export default function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleClickButton1 = () => {
setCount1(count1 + 1);
};
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
return (
<div>
<div>
<Button onClickButton={handleClickButton1}>Button1</Button>
</div>
<div>
<Button onClickButton={handleClickButton2}>Button2</Button>
</div>
</div>
);
}
複製程式碼
// Button.jsx
import React from 'react';
const Button = ({ onClickButton, children }) => {
return (
<>
<button onClick={onClickButton}>{children}</button>
<span>{Math.random()}</span>
</>
);
};
export default React.memo(Button);
複製程式碼
在案例中可以點選 Button1 和 Button2 兩個按鈕來檢視效果,點選 Button1 的時候只會更新 Button1 後面的內容,點選 Button2 會將兩個按鈕後的內容都更新。這就表示我在點選 Button2 的時候導致了兩個按鈕內都重新渲染了。
這裡或許會注意到
React.memo
這個方法,此方法內會對 props 做一個淺層比較,如果如果 props 沒有發生改變,則不會重新渲染此元件。
const a = () => {};
const b = () => {};
a === b; // false
複製程式碼
上述程式碼可以看到我們兩個一樣的函式卻是不相等的(這是個廢話,我相信能看到這的人都知道,所以不做解釋了)。
const [count1, setCount1] = useState(0);
// ...
const handleClickButton1 = () => {
setCount1(count1 + 1);
};
// ...
return <Button onClickButton={handleClickButton1}>Button1</Button>
複製程式碼
回頭再看上面的 Button
元件都需要一個 onClickButton 的 props ,儘管元件內部有用 React.memo
來做優化,但是我們宣告的 handleClickButton1
是直接定義了一個方法,這也就導致只要是父元件重新渲染(狀態或者props更新)就會導致這裡宣告出一個新的方法,新的方法和舊的方法儘管長的一樣,但是依舊是兩個不同的物件,React.memo
對比後發現物件 props 改變,就重新渲染了。
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
複製程式碼
上述程式碼我們的方法使用 useCallback 包裝了一層,並且後面還傳入了一個 [count2]
變數,這裡 useCallback 就會根據 count2
是否發生變化,從而決定是否返回一個新的函式,函式內部作用域也隨之更新。
由於我們的這個方法只依賴了 count2
這個變數,而且 count2
只在點選 Button2 後才會更新 handleClickButton2
,所以就導致了我們點選 Button1 不重新渲染 Button2 的內容。
注意點
import React, { useState, useCallback } from 'react';
import Button from './Button';
export default function App() {
const [count2, setCount2] = useState(0);
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, []);
return (
<Button
count={count2}
onClickButton={handleClickButton2}
>Button2</Button>
);
}
複製程式碼
我們調整了一下程式碼,將 useCallback 依賴的第二個引數變成了一個空的陣列,這也就意味著這個方法沒有依賴值,將不會被更新。且由於 JS 的靜態作用域導致此函式內 count2
永遠都 0
。
可以點選多次 Button2 檢視變化,會發現 Button2 後面的值只會改變一次。因為上述函式內的 count2
永遠都是 0
,就意味著每次都是 0 + 1
,Button 所接受的 count
props,也只會從 0
變成 1
且一直都將是 1
,而且 handleClickButton2
也因沒有依賴項不會返回新的方法,就導致 Button 只會因 count
改變而更新一次後就不會被重新渲染。
useMemo
useMemo 的作用
官方文件:
Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed.
簡單來說就是傳遞一個建立函式和依賴項,建立函式會需要返回一個值,只有在依賴項發生改變的時候,才會重新呼叫此函式,返回一個新的依賴值。
useMemo 的應用
useMemo 與 useCallback 很像,根據上述 useCallback 已經可以想到 useMemo 也能針對傳入子元件的值進行快取優化,當然這個值必須是一個物件,如果不是物件而是一些簡單型別的如字串等,那麼沒更改 React.memo
也能對比出來,下面就直接舉個 ? 對比一下。
// ...
const [count, setCount] = useState(0);
const userInfo = {
// ...
age: count,
name: 'Jace'
}
return <UserCard userInfo={userInfo}>
複製程式碼
// ...
const [count, setCount] = useState(0);
const userInfo = useMemo(() => {
return {
// ...
name: "Jace",
age: count
};
}, [count]);
return <UserCard userInfo={userInfo}>
複製程式碼
很明顯的上面的 userInfo 每次都將是一個新的物件,無論 count
發生改變沒,都會導致 UserCard 重新渲染,而下面的則會在 count
改變後才會返回新的物件。
實際上 useMemo 的作用不止於此,根據官方文件內介紹,它主要的功能應該是:
This optimization helps to avoid expensive calculations on every render.
可以吧一些昂貴的計算邏輯放到 useMemo 中,只有當依賴值發生改變的時候才去更新。
const num = useMemo(() => {
let num = 0;
// 這裡使用 count 針對 num 做一些很複雜的計算,當 count 沒改變的時候,元件重新渲染就會直接返回之前快取的值。
return num;
}, [count]);
return <div>{num}</div>
複製程式碼
也能在很多情況將兩種情況結合起來用。
結束。