父组件处理子组件传入函数时,是否需用useCallback包裹handleDoIt?
问题描述
我知道当关注父组件渲染时的引用相等性时,应该考虑使用useCallback,但不确定在父组件(App)向子组件(Wrapper)传入函数、子组件内部处理该函数的场景下,子组件里的处理函数是否需要用useCallback包裹。以下是相关代码:
原代码
import { ReactNode, useCallback, useState } from "react"; interface WrapperProps { children: ReactNode; onDoIt: () => Promise<void>; } type Status = "Do It" | "Doing It..." | "Did It"; export const Wrapper: React.FC<WrapperProps> = ({ children, onDoIt }) => { const [status, setStatus] = useState<Status>("Do It"); const handleDidIt = () => { setStatus("Did It"); }; const handleDoIt = async () => { setStatus("Doing It..."); await onDoIt(); handleDidIt(); }; return ( <> <button onClick={handleDoIt} disabled={status !== "Do It"}> {status} </button> <hr /> {children} </> ); }; export default function App() { const [result, setResult] = useState<string>(""); const doIt = (): Promise<void> => { return new Promise((resolve) => { window.setTimeout(() => { setResult("Done, thanks"); return resolve(); }, 1000); }); }; return ( <Wrapper onDoIt={doIt}> <h1>Hello CodeSandbox</h1> <p>{result}</p> </Wrapper> ); }
疑问:此处的handleDoIt函数是否应该用useCallback包裹?以下是包裹后的示例:
const handleDoItCallback = useCallback(async () => { setStatus("Doing It..."); await onDoIt(); handleDidIt(); }, [onDoIt]);
回答
不需要给handleDoIt包裹useCallback,原因如下:
使用场景不匹配:
handleDoIt是作为组件内部原生按钮的onClick回调存在的,并非作为props传递给子组件。React对原生DOM事件回调的处理中,每次渲染创建新函数的性能影响微乎其微,除非组件处于极端高频渲染的场景,否则完全不需要为此缓存函数引用。缓存无效:
handleDoIt依赖的onDoIt是从App组件传入的props,而App中的doIt函数每次渲染都会重新创建(未用useCallback包裹),导致onDoIt的引用每次都是新的。即使给handleDoIt加上useCallback,由于依赖数组中的onDoIt引用不稳定,useCallback每次都会生成新的函数,起不到任何缓存作用。
补充优化建议
如果真的想优化引用稳定性,应该先给App中的doIt函数包裹useCallback,让onDoIt的引用保持稳定:
// App组件中的doIt优化 const doIt = useCallback((): Promise<void> => { return new Promise((resolve) => { window.setTimeout(() => { setResult("Done, thanks"); return resolve(); }, 1000); }); }, []); // 依赖为空,因为只用到setResult(useState的setter引用稳定)
但即使做了这个优化,当前场景下handleDoIt依然不需要useCallback——只有当handleDoIt需要作为props传递给子组件,或者Wrapper组件被React.memo包裹且需要稳定props来避免不必要重渲染时,才需要考虑使用useCallback缓存它。
内容的提问来源于stack exchange,提问作者Getting There

