簡單的說
都是用來監聽數據變化 來進行控制渲染、減少不必要的渲染 、優化性能
usecallback()是用來監聽數據變化從而調用方法
usememo()是用來監聽數據變化從而改變數據 使用return返回變化的數據 當然return 也可以返回方法 所以usememo()可以代替usecallback()
下面詳解
useCallback:緩存回調函數
在 React 函數組件中,每一次 UI 的變化,都是通過重新執行整個函數來完成的,這和傳統的 Class 組件有很大區別:函數組件中并沒有一個直接的方式在多次渲染之間維持一個狀態。 比如下面的代碼中,我們在加號按鈕上定義了一個事件處理函數,用來讓計數器加 1。但是因為定義是在函數組件內部,因此在多次渲染之間,是無法重用 handleIncrement 這個函數的,而是每次都需要創建一個新的:
function Counter() {const [count, setCount] = useState(0);const handleIncrement = () => setCount(count + 1);// ...return <button onClick={handleIncrement}>+</button>
}
你不妨思考下這個過程。每次組件狀態發生變化的時候,函數組件實際上都會重新執行一遍。在每次執行的時候,實際上都會創建一個新的事件處理函數 handleIncrement。
這個事件處理函數中呢,包含了 count 這個變量的閉包,以確保每次能夠得到正確的結果。 這也意味著,即使 count 沒有發生變化,但是函數組件因為其它狀態發生變化而重新渲染時,這種寫法也會每次創建一個新的函數。
創建一個新的事件處理函數,雖然不影響結果的正確性,但其實是沒必要的。因為這樣做不僅增加了系統的開銷,更重要的是:每次創建新函數的方式會讓接收事件處理函數的組件,需要重新渲染。
比如這個例子中的 button 組件,接收了 handleIncrement ,并作為一個屬性。如果每次都是一個新的,那么這個 React 就會認為這個組件的 props 發生了變化,從而必須重新渲染。因此,我們需要做到的是:**只有當 count 發生變化時,我們才需要重新定一個回調函數。**而這正是 useCallback 這個 Hook 的作用。
import React, { useState, useCallback } from 'react';
function Counter() {const [count, setCount] = useState(0);const handleIncrement = useCallback(() => setCount(count + 1),[count], // 只有當 count 發生變化時,才會重新創建回調函數);// ...return <button onClick={handleIncrement}>+</button>
}
在這里,我們把 count 這個 state ,作為一個依賴傳遞給 useCallback。這樣,只有 count 發生變化的時候,才需要重新創建一個回調函數,這樣就保證了組件不會創建重復的回調函數。而接收這個回調函數作為屬性的組件,也不會頻繁地需要重新渲染。
useMemo:緩存計算的結果
如果某個數據是通過其它數據計算得到的,那么只有當用到的數據,也就是依賴的數據發生變化的時候,才應該需要重新計算。
舉個例子,對于一個顯示用戶信息的列表,現在需要對用戶名進行搜索,且 UI 上需要根據搜索關鍵字顯示過濾后的用戶,那么這樣一個功能需要有兩個狀態:
1.用戶列表數據本身:來自某個請求。
2.搜索關鍵字:用戶在搜索框輸入的數據。
無論是兩個數據中的哪一個發生變化,都需要過濾用戶列表以獲得需要展示的數據。那么如果不使用 useMemo 的話,就需要用這樣的代碼實現:
import React, { useState, useEffect } from "react";export default function SearchUserList() {const [users, setUsers] = useState(null);const [searchKey, setSearchKey] = useState("");useEffect(() => {const doFetch = async () => {// 組件首次加載時發請求獲取用戶數據const res = await fetch("https://reqres.in/api/users/");setUsers(await res.json());};doFetch();}, []);let usersToShow = null;if (users) {// 無論組件為何刷新,這里一定會對數組做一次過濾的操作usersToShow = users.data.filter((user) =>user.first_name.includes(searchKey),);}return (<div><inputtype="text"value={searchKey}onChange={(evt) => setSearchKey(evt.target.value)}/><ul>{usersToShow &&usersToShow.length > 0 &&usersToShow.map((user) => {return <li key={user.id}>{user.first_name}</li>;})}</ul></div>);
}
在這個例子中,無論組件為何要進行一次重新渲染,實際上都需要進行一次過濾的操作。但其實你只需要在 users 或者 searchKey 這兩個狀態中的某一個發生變化時,重新計算獲得需要展示的數據就行了。那么,這個時候,我們就可以用 useMemo 這個 Hook 來實現這個邏輯,緩存計算的結果
//…
// 使用 userMemo 緩存計算的結果
const usersToShow = useMemo(() => {if (!users) return null;return users.data.filter((user) => {return user.first_name.includes(searchKey));}}, [users, searchKey]);
//...
可以看到,通過 useMemo 這個 Hook,可以避免在用到的數據沒發生變化時進行的重復計算。雖然例子展示的是一個很簡單的場景,但如果是一個復雜的計算,那么對于提升性能會有很大的幫助。
這也是 userMemo 的一大好處:避免重復計算。 除了避免重復計算之外,useMemo 還有一個很重要的好處:避免子組件的重復渲染。比如在例子中的 usersToShow 這個變量,如果每次都需要重新計算來得到,那么對于 UserList 這個組件而言,就會每次都需要刷新,因為它將 usersToShow 作為了一個屬性。而一旦能夠緩存上次的結果,就和 useCallback 的場景一樣,可以避免很多不必要的組件刷新。
這個時候,如果我們結合 useMemo 和 useCallback 這兩個 Hooks 一起看,會發現一個有趣的特性,那就是 useCallback 的功能其實是可以用 useMemo 來實現的。比如下面的代碼就是利用 useMemo 實現了 useCallback 的功能:
const myEventHandler = useMemo(() => {// 返回一個函數作為緩存結果return () => {// 在這里進行事件處理}}, [dep1, dep2]);
理解了這一點,相信你一下子會對這兩個 Hooks 的機制有更進一步的認識,也就不用死記硬背兩個 API 都是干嘛的了,因為從本質上來說,它們只是做了同一件事情:建立了一個綁定某個結果到依賴數據的關系。只有當依賴變了,這個結果才需要被重新得到。
深入討論usecallback()
開發過程中我遇到了
const toggleTheme = useCallback(() =>
{
setTheme((theme) =>(theme === "light" ? "dark" : "light"));
}, []);
疑問?為什么沒有依賴項 不需要依賴theme 獲取最新的theme的值么
答:
useCallback 的依賴項數組是用于確定何時重新計算緩存的函數的。如果依賴項數組中有任何值發生變化,那么函數將被重新創建。
然而,對于 toggleTheme 函數,你并不需要在依賴項數組中包含 theme。這是因為你是在函數內部直接讀取 theme 的最新值,而不是依賴于外部的 theme 變量。這是 setTheme 函數的一個特性:當你傳遞一個函數給 setTheme,React 會將 當前的 theme 狀態值作為那個函數的參數。
所以,即使 theme 值在多次渲染之間發生變化,toggleTheme 函數總是能讀取到最新的 theme 值,因此不需要在依賴項數組中包含 theme。
這種模式在你想要根據當前狀態值更新狀態時是非常有用的,因為它可以確保你總是使用最新的狀態值,而不是在閉包中緩存的舊值。