[英]When to use useCallback, useMemo and useEffect?
useCallback
、 useMemo
和useEffect
之間的主要區別是什么?
舉例說明何時使用它們中的每一個。
一個簡短的解釋。
它是類組件生命周期方法componentDidMount
、 componentWillUnmount
、 componentDidUpdate
等的替代方法。您還可以使用它來在依賴項更改時創建副作用,即“如果某些變量發生更改,請執行此操作”。
在每次渲染時,功能組件內的所有內容都將再次運行。 如果子組件依賴於父組件的函數,即使該函數“不會改變”(引用改變,但函數所做的不會改變),每次父組件重新渲染時,子組件也會重新渲染t)。
它用於通過避免來自子級的不必要渲染來進行優化,使函數僅在依賴項更改時更改引用。 當函數是副作用的依賴項時,您應該使用它,例如useEffect
。
它將在每次渲染上運行,但帶有緩存值。 它只會在某些依賴項更改時使用新值。 當您有昂貴的計算時,它用於優化。 這也是解釋它的一個很好的答案。
useEffect()
將允許您根據發送給組件的依賴關系在組件上創建副作用。
function Example() { const [count, setCount] = React.useState(0); React.useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } ReactDOM.render(<Example />, document.getElementById('root'))
<script src="https://unpkg.com/react@16.8.0/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16.8.0/umd/react-dom.development.js"></script> <div id="root"></div>
上面的例子取自 React 的文檔。 您可以看到,每次單擊按鈕時,它都會觸發計數字段的更新(使用 setCount()),然后,依賴於計數變量的效果將觸發頁面標題的更新。
useCallback()
將返回一個記憶回調。 通常,如果您有一個接收函數 prop 的子組件,則在每次重新渲染父組件時,都會重新執行此函數; 通過使用useCallback()
您可以確保僅在其依賴項數組上的任何值發生更改時才重新執行此函數。
function ExampleChild({ callbackFunction }) { const [value, setValue] = React.useState(0); React.useEffect(() => { setValue(value + 1) }, [callbackFunction]); return (<p>Child: {value}</p>); } function ExampleParent() { const [count, setCount] = React.useState(0); const [another, setAnother] = React.useState(0); const countCallback = React.useCallback(() => { return count; }, [count]); return ( <div> <ExampleChild callbackFunction={countCallback} /> <button onClick={() => setCount(count + 1)}> Change callback </button> <button onClick={() => setAnother(another + 1)}> Do not change callback </button> </div> ) } ReactDOM.render(<ExampleParent />, document.getElementById('root'));
<script src="https://unpkg.com/react@16.8.0/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16.8.0/umd/react-dom.development.js"></script> <div id="root"></div>
useMemo()
將返回一個記憶值,該值是傳遞參數的結果。 這意味着useMemo()
將對某個參數進行一次計算,然后它將從緩存中為同一參數返回相同的結果。
當您需要處理大量數據時,這非常有用。
function ExampleChild({ value }) { const [childValue, setChildValue] = React.useState(0); React.useEffect(() => { setChildValue(childValue + 1); }, [value]) return <p>Child value: {childValue}</p>; } function ExampleParent() { const [value, setValue] = React.useState(0); const heavyProcessing = () => { // Do some heavy processing with the parameter console.log(`Cached memo: ${value}`); return value; }; const memoizedResult = React.useMemo(heavyProcessing, [value]); return ( <div> <ExampleChild value={memoizedResult} /> <button onClick={() => setValue(value + 1)}> Change memo </button> </div> ) } ReactDOM.render(<ExampleParent />, document.getElementById('root'));
<script src="https://unpkg.com/react@16.8.0/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16.8.0/umd/react-dom.development.js"></script> <div id="root"></div>
最低限度的解釋:
使用效果:
每當您有一些邏輯作為對狀態更改的反應或在更改即將發生之前執行。
useEffect(() => {
// execute when state changed
() => {
// execute before state is changed
}
}, [state]);
或者在沒有依賴的情況下:
useEffect(() => {
// execute when component has mounted
() => {
// execute when component will unmount
}
}, []);
使用回調:
每當你有一個依賴於某些狀態的函數時。 這個鈎子用於性能優化,並防止組件內的函數被重新分配,除非依賴狀態發生變化。
const myFunction = useCallback(() => {
// execute your logic for myFunction
}, [state]);
如果沒有 useCallback,myFunction 將在每次渲染時重新分配。 因此,它使用更多的計算時間,就像使用 useCallback 一樣。
使用備忘錄
每當您有一個取決於特定狀態的值時。 與 useCallback 相同,useMemo 用於減少重新分配以優化性能。
const myValue = useMemo(() => {
// return calculated value
}, [state]);
與 useCallback 相同,myValue 僅在狀態改變時分配,因此會減少計算時間。 否則 myValue 將在每次渲染時重新分配。
! 模仿 componentWillMount 生命周期的技巧
useMemo(() => {
// execute componentWillMount logic
]}, []);
因為 useEffect 在第一次渲染之后調用,然后在每次依賴更改時調用。 它永遠不會在第一次渲染之前運行。 useMemo 與您的 JS 內聯執行,因此將在它到達您的組件返回語句之前執行。
!注意:帶有 useCallback 的函數和帶有 useMemo 的值可以用作 useCallback、useMemo 和 useEffect 中的依賴項。 強烈建議使用這些鈎子,以便在您的組件中擁有結構良好且可讀的狀態流。 這些鈎子不會觸發渲染。 只有 useState 和 useReducer 可以!
如果你想保持不觸發重新渲染的狀態或任何上述解釋的鈎子,你可以使用 useRef。 useRef將在渲染上保持一致的值,而不會觸發任何狀態相關的值或效果。
知道何時使用這些功能很好,但我想知道它們之間的實際區別是什么! 這是我發現的:
useMemo
立即運行代碼,因此返回值可用於后面的代碼。 這意味着它在第一次渲染之前運行,因此您用來訪問 HTML 組件的任何useRef
在初始運行時都將不可用。 (但是您可以將ref.current
添加到useMemo
依賴項中,以便在第一次渲染后再次運行useMemo
代碼,當useRef
值可用時)。 由於返回值可用於直接跟隨它的代碼,這就是為什么建議用於不需要在每次渲染時重新運行的復雜計算的原因,因為返回值立即可用可以使您不必弄亂狀態現在存儲值並稍后訪問它 - 只需獲取useMemo()
的返回值並立即使用它。useEffect
不會立即運行,而是在第一次渲染之后運行。 這意味着任何引用 HTML 元素的useRef
值在第一次運行時都是有效的。 由於它在函數中的所有代碼都完成並呈現后運行,因此沒有返回值的意義,因為沒有進一步運行的代碼可以使用它。 useEffect
代碼可以執行任何可見操作的唯一方法是更改狀態以導致重新渲染,或者直接修改 DOM。useCallback
與useMemo
相同,除了它記住函數本身而不是它的返回值。 這意味着useCallback
函數不會立即運行,但可以稍后運行(或根本不運行),而useMemo
立即運行其函數並只保存其返回值供以后使用。 與useMemo
不同,這不適用於復雜的計算,因為每次使用時代碼都會再次運行。 例如,如果我們將相同的函數傳遞給useMemo
和useCallback
:
let input = 123;
const output = useMemo(() => {
return input + 1;
}, [
input,
]);
// The above function has now run and its return value is available.
console.log( output ); // 124
input = 125; // no effect as the function has already run
console.log( output ); // 124
let input = 123;
const output = useCallback(() => {
return input + 1;
}, [
input,
]);
// The above function has not run yet but we can run it now.
console.log( output() ); // 124
input = 125; // changes the result as the function is running again
console.log( output() ); // 126
在這里, useCallback
已經記住了該函數,並且會在未來的渲染中繼續返回原始函數,直到依賴項發生變化,而useMemo
實際上立即運行該函數並且只記住它的返回值。
useCallback()
和useMemo()
提供可以立即使用的返回值,而useEffect()
則不提供,因為它的代碼要在渲染完成后很晚才運行。
useEffect
在組件掛載、卸載及其任何依賴項更改時調用。
可用於在組件mounted
時獲取數據,在組件mounts
和unmounts
時subscribe
和unsubscribe
事件流(想想 rxjs)。
const [userId, updateUser] = useState(1);
useEffect(()=>{
//subscription
const sub = getUser(userId).subscribe(user => user);
// cleanup
return () => {
sub.unsubscribe();
}
},[userId]) // <-- Will get called again when userId changes
也可用於不需要清理的一次性方法調用
useEffect(()=>{
oneTimeData();
},[]); // pass empty array to prevent being called multiple times
useCallback
有不想在每個組件渲染上重新創建的功能?
想要一個在組件掛載或卸載時不被調用的函數?
使用useCallback
const [val, updateValue] = useState(0);
const Compo = () => {
/* inc and dec will be re-created on every component render.
Not desirable a function does very intensive work.
*/
const inc = () => updateValue(x => x + 1);
const dec = () => updateValue(x => x - 1);
return render() {
<Comp1 onClick={inc} />
<Comp2 onClick={dec} />
}
}
useCallback
來救援
const [val, updateValue] = useState(0);
const Compo = () => {
const callbackInc = useCallback(() => {
setCount(currentVal => currentVal + 1);
}, []);
const callbackDec = useCallback(() => {
setCount(currentVal => currentVal - 1);
}, []);
return render() {
<Comp1 onClick={callbackInc} />
<Comp2 onClick={callbackDec} />
}
}
如果傳遞給setCount
的參數不是函數,那么您希望useCallback
“注意”的變量必須在依賴項數組中指定,否則不會有任何更改效果。
const callbackInc = useCallback(() => {
setCount(val + 1); // val is an 'outside' variable therefore must be specified as a dependency
}, [val]);
useMemo
進行繁重的處理並想要記憶(緩存)結果? 使用使用useMemo
/*
heavyProcessFunc will only be called again when either val or val2 changes
*/
const result = useMemo(heavyProcessFunc(val, val2),[val,val2])
所有這些鈎子都有相同的目標:避免冗余的組件重建(以及鈎子內的東西的重新執行)。
useEffect
返回任何內容(void),因此適用於此類情況。
useCallback
返回一個函數,稍后將在組件中使用該函數。 與普通函數聲明不同,它不會觸發組件重建,除非其依賴項發生變化。
useMemo
只是useMemo
的另一種useCallback
。
這是迄今為止我見過的最好的解釋。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.