具有依赖性的 useCallback 与使用 ref 调用函数的最新版本
useCallback with dependency vs using a ref to call the last version of the function
在进行代码审查时,我遇到了这个自定义挂钩:
import { useRef, useEffect, useCallback } from 'react'
export default function useLastVersion (func) {
const ref = useRef()
useEffect(() => {
ref.current = func
}, [func])
return useCallback((...args) => {
return ref.current(...args)
}, [])
}
这个钩子是这样使用的:
const f = useLastVersion(() => { // do stuff and depends on props })
基本上,与 const f = useCallBack(() => { // do stuff }, [dep1, dep2])
相比,这避免了声明依赖项列表,并且 f
永远不会更改,即使其中一个依赖项发生变化。
我不知道如何看待这段代码。我不明白与 useCallback
.
相比,使用 useLastVersion
有什么缺点
这个问题实际上已经或多或少地在文档中得到了回答:https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback
有趣的部分是:
Also note that this pattern might cause problems in the concurrent mode. We plan to provide more ergonomic alternatives in the future, but the safest solution right now is to always invalidate the callback if some value it depends on changes.
阅读也很有趣:https://github.com/facebook/react/issues/14099 and https://github.com/reactjs/rfcs/issues/83
当前的建议是使用提供程序来避免在 props 中传递回调,如果我们担心这会导致过多的重新渲染。
我的观点如评论中所述,当依赖项更改过于频繁时(在 useEffect
/useCallback
dep 数组),使用普通函数是最好的选择(无开销)。
这个钩子隐藏了使用它的组件的渲染,但渲染来自其父级中的 useEffect
。
如果我们总结我们得到的渲染计数:
- Ref + useCallback (the hook): Render in
Component
(due to value
) + Render in hook (useEffect
), 共2.
仅 - useCallback:在
Component
中呈现(由于 value
)+ 在 Counter
中呈现(由于 value
更改而导致的函数引用更改),总共 2 个.
- 普通函数:在
Component
中渲染 + 在 Counter
中渲染:每次渲染都有新函数,总共 2 个。
但是在 useEffect
或 useCallback
.
中进行浅比较会产生额外的开销
实际例子:
function App() {
const [value, setValue] = useState("");
return (
<div>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
type="text"
/>
<Component value={value} />
</div>
);
}
function useLastVersion(func) {
const ref = useRef();
useEffect(() => {
ref.current = func;
console.log("useEffect called in ref+callback");
}, [func]);
return useCallback((...args) => {
return ref.current(...args);
}, []);
}
function Component({ value }) {
const f1 = useLastVersion(() => {
alert(value.length);
});
const f2 = useCallback(() => {
alert(value.length);
}, [value]);
const f3 = () => {
alert(value.length);
};
return (
<div>
Ref and useCallback:{" "}
<MemoCounter callBack={f1} msg="ref and useCallback" />
Callback only: <MemoCounter callBack={f2} msg="callback only" />
Normal: <MemoCounter callBack={f3} msg="normal" />
</div>
);
}
function Counter({ callBack, msg }) {
console.log(msg);
return <button onClick={callBack}>Click Me</button>;
}
const MemoCounter = React.memo(Counter);
作为旁注,如果目的只是找到具有最小渲染的 input
的长度,阅读 inputRef.current.value
将是解决方案。
在进行代码审查时,我遇到了这个自定义挂钩:
import { useRef, useEffect, useCallback } from 'react'
export default function useLastVersion (func) {
const ref = useRef()
useEffect(() => {
ref.current = func
}, [func])
return useCallback((...args) => {
return ref.current(...args)
}, [])
}
这个钩子是这样使用的:
const f = useLastVersion(() => { // do stuff and depends on props })
基本上,与 const f = useCallBack(() => { // do stuff }, [dep1, dep2])
相比,这避免了声明依赖项列表,并且 f
永远不会更改,即使其中一个依赖项发生变化。
我不知道如何看待这段代码。我不明白与 useCallback
.
useLastVersion
有什么缺点
这个问题实际上已经或多或少地在文档中得到了回答:https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback
有趣的部分是:
Also note that this pattern might cause problems in the concurrent mode. We plan to provide more ergonomic alternatives in the future, but the safest solution right now is to always invalidate the callback if some value it depends on changes.
阅读也很有趣:https://github.com/facebook/react/issues/14099 and https://github.com/reactjs/rfcs/issues/83
当前的建议是使用提供程序来避免在 props 中传递回调,如果我们担心这会导致过多的重新渲染。
我的观点如评论中所述,当依赖项更改过于频繁时(在 useEffect
/useCallback
dep 数组),使用普通函数是最好的选择(无开销)。
这个钩子隐藏了使用它的组件的渲染,但渲染来自其父级中的 useEffect
。
如果我们总结我们得到的渲染计数:
- Ref + useCallback (the hook): Render in
Component
(due tovalue
) + Render in hook (useEffect
), 共2.
仅 - useCallback:在
Component
中呈现(由于value
)+ 在Counter
中呈现(由于value
更改而导致的函数引用更改),总共 2 个. - 普通函数:在
Component
中渲染 + 在Counter
中渲染:每次渲染都有新函数,总共 2 个。
但是在 useEffect
或 useCallback
.
实际例子:
function App() {
const [value, setValue] = useState("");
return (
<div>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
type="text"
/>
<Component value={value} />
</div>
);
}
function useLastVersion(func) {
const ref = useRef();
useEffect(() => {
ref.current = func;
console.log("useEffect called in ref+callback");
}, [func]);
return useCallback((...args) => {
return ref.current(...args);
}, []);
}
function Component({ value }) {
const f1 = useLastVersion(() => {
alert(value.length);
});
const f2 = useCallback(() => {
alert(value.length);
}, [value]);
const f3 = () => {
alert(value.length);
};
return (
<div>
Ref and useCallback:{" "}
<MemoCounter callBack={f1} msg="ref and useCallback" />
Callback only: <MemoCounter callBack={f2} msg="callback only" />
Normal: <MemoCounter callBack={f3} msg="normal" />
</div>
);
}
function Counter({ callBack, msg }) {
console.log(msg);
return <button onClick={callBack}>Click Me</button>;
}
const MemoCounter = React.memo(Counter);
作为旁注,如果目的只是找到具有最小渲染的 input
的长度,阅读 inputRef.current.value
将是解决方案。