我博客的一位讀者在Facebook上聯系到我,提出了一個有趣的問題。他說,他的隊友不管在什么情況下,都會把每一個回調函數封裝在 useCallback() 里面。
import react, { useCallback } from 'react'; function MyComponent() { const handleClick = useCallback(() => { // handle the click event }, []); return <MyChild onClick={handleClick} />; }
“每個回調函數都應該被記住,以防止使用回調函數的子組件被無用地重新渲染”,這是他的隊友的理由。
這句話與事實相去甚遠。此外,useCallback() 的這種用法會使組件變慢,從而損害性能。
在本文中,我將解釋如何正確使用 useCallback()。
1.了解函數相等性檢查
在深入研究 useCallback() 用法之前,讓我們區分一下鈎子要解決的問題:函數相等性檢查。
讓我們定義一個名為 factory() 的函數,該函數返回函數:
function factory() { return (a, b) => a + b; } const sum1 = factory(); const sum2 = factory(); sum1(1, 2); // => 3 sum2(1, 2); // => 3 sum1 === sum2; // => false sum1 === sum1; // => true
sum1 和 sum2 是將兩個數字相加的函數,它們是由 factory() 函數創建的。
函數 sum1 和 sum2 共享相同的代碼源,但是它們是不同的對象,比較它們 sum1 === sum2 結果為 false。
這就是JavaScript的工作方式,對象(包括函數對象)僅等於其自身。
2.useCallback() 的目的
共享相同代碼的不同函數實例往往在React組件內部創建。
當 React 組件主體創建一個函數(例如回調或事件處理程序)時,這個函數會在每次渲染時重新創建。
import React from 'react'; function MyComponent() { // handleClick在每次渲染時重新創建 const handleClick = () => { console.log('Clicked!'); }; // ... }
handleClick 在 MyComponent 的每次渲染中都是一個不同的函數對象。
因為內聯函數很“便宜”,所以在每次渲染時重新創建函數不是問題,每個組件有幾個內聯函數是可以接受的。
然而,在某些情況下,你需要保留一個函數的一個實例:
- 包裝在 React.memo()(或 shouldComponentUpdate )中的組件接受回調prop。
- 當函數用作其他hooks的依賴項時 useEffect(...,[callback])
這就是當 useCallback(callbackFun, deps) 幫助你的情況:給出相同的依賴值 deps,hook在兩次渲染之間返回相同的函數實例。
import React, { useCallback } from 'react'; function MyComponent() { // handleClick是同一個函數對象 const handleClick = useCallback(() => { console.log('Clicked!'); }, []); // ... }
handleClick 變量將在不同的 MyComponent 的渲染之間始終擁有相同的回調函數對象。
3.一個好用例
想象一下,你有一個呈現大的項目列表組件:
import React from 'react'; function MyBigList({ items, handleClick }) { const map = (item, index) => ( <div onClick={() => handleClick(index)}>{item}</div>; ); return <div>{items.map(map)}</div>; } export const MyBigList = React.memo(MyBigList);
MyBigList 渲染了一個項目列表,要知道這個列表可能很大,可能有幾百個項目。要保留重新渲染的列表,可以將其封裝到 React.memo 中。
單擊一個項目時,MyBigList 的父組件需要提供項目列表和處理程序功能。
import React from 'react'; import useSearch from './fetch-items'; function MyParent({ term }) { const handleClick = useCallback((item) => { console.log('You clicked ', item); }, [term]); const items = useSearch(term); return ( <MyBigList items={items} handleClick={handleClick} /> ); }
handleClick 回調由 useCallback() 記憶。只要 term 變量保持不變,useCallback() 就會返回相同的函數對象。
即使由於某些原因重新啟用了 MyParent 組件,handleClick 仍保持不變,並且不會破壞 MyBigList的記憶。
4.一個“壞”的用例
讓我們回顧一下本文簡介中的示例:
import React, { useCallback } from 'react'; function MyComponent() { const handleClick = useCallback(() => { // handle the click event }, []); return <MyChild onClick={handleClick} />; } function MyChild ({ onClick }) { return <button onClick={onClick}>I am a child</button>; }
記住 handleClick 是否有意義?
沒有,因為調用 useCallback() 需要很多工作,每次渲染 MyComponent 時,都會調用 useCallback() Hook。
從內部來講,React確保返回相同的對象函數。即便如此,內聯函數仍然在每次渲染時創建,useCallback() 只是跳過了它。
即使用 useCallback() 返回相同的函數實例,也不會帶來任何好處,因為優化要比沒有優化花費更多。
不要忘記增加的代碼復雜性,你必須確保 useCallback() 的 deps 與您在 memoized 回調中使用的 deps 保持同步。
只需接受每次重新渲染時創建新的函數:
import React, { useCallback } from 'react'; function MyComponent() { const handleClick = () => { // handle the click event }; return <MyChild onClick={handleClick} />; } function MyChild ({ onClick }) { return <button onClick={onClick}>I am a child</button>; }
豌豆資源搜索網站https://55wd.com 電腦刺綉綉花廠 ttp://www.szhdn.com
5.總結
任何優化都會增加復雜性,任何過早添加的優化都會帶來風險,因為優化后的代碼可能會多次更改。