useEffect依赖触发逻辑及useDependentValue与useMemo性能对比
代码示例
#1
import { heavyCalculate } from './helpers'; ... const [data, setData] = useState(INITIAL_DATA); // 假设 data 是大型对象 const dataDependentValue = useMemo(() => { console.log("recalculating dataDependentValue"); return heavyCalculate(data); // 一些“耗时”计算 }, [data]); ...
#2
自定义钩子实现
// 自定义钩子:当依赖数组变化时,返回处理器函数计算的值 // 注意:钩子需声明在 React 组件外部 const useDependentValue = (processor, deps) => { console.log("Call `useDependentValue`", { deps }); const [value, setValue] = useState(() => { console.log("Initial `useState` call"); return processor(); }); useEffect(() => { const updateValue = () => { setValue(() => { console.log("Update"); return processor(); }); }; updateValue(); }, deps); return value; };
组件中使用自定义钩子
import { useDependentValue } from './hooks'; import { heavyCalculate } from './helpers'; ... const [data, setData] = useState(INITIAL_DATA); // 假设 data 是大型对象 const dataDependentValue = useDependentValue(() => { console.log("recalculating dataDependentValue"); return heavyCalculate(data); // 一些“耗时”计算 }, [data]); ...
技术问询
- 是否存在某些场景,使用
useDependentValue替代useMemo能获得更优的CPU时间/内存性能? - 带有非空依赖数组的
useEffect是如何决定是否调用传入的effect函数的?其逻辑是否与useMemo一致(对比前后两次渲染的依赖值),还是存在其他差异?
提问背景:若useEffect同样会缓存上一次渲染的依赖并与新依赖对比,那么useDependentValue钩子的实用价值不大;反之,若useEffect不缓存依赖也不进行前后对比,那么对于依赖为大型对象的场景,useDependentValue或许能因避免耗时的依赖对比而拥有更快的运行速度。
解答
问题1:useDependentValue是否存在性能优势场景?
不存在,绝大多数场景下它的性能反而比useMemo更差,核心原因有两点:
- 额外重渲染开销:
useDependentValue依赖useEffect触发setValue,这会导致组件在初始渲染后额外触发一次重渲染(effect执行后更新状态,组件需再次渲染);而useMemo是在渲染期间直接计算值,不会产生额外渲染成本。 - 依赖对比逻辑无差异:
useEffect的依赖对比逻辑和useMemo完全一致,大型对象的浅对比开销两者都要承担,不存在useDependentValue能避免对比的情况。
唯一极端场景:如果heavyCalculate函数极端耗时,且你能接受值延迟更新(useDependentValue的结果是在渲染后才更新的),此时可以把计算放到effect里不阻塞渲染,但这是渲染优先级调整,不是性能更优,而且用useMemo配合useDeferredValue或直接在effect中处理更合适,自定义钩子没有额外优势。
问题2:useEffect的依赖对比逻辑与useMemo是否一致?
完全一致。React内部对useEffect、useMemo、useCallback的依赖数组使用同一套浅对比逻辑:
- 每次渲染时缓存当前依赖数组;
- 下一次渲染时,将新依赖数组和缓存的旧数组逐项用
Object.is做浅对比; - 只要有任意一项依赖变化,
useEffect就会执行effect函数,useMemo就会重新计算值。
所以你的担心不成立,useDependentValue并没有绕过依赖对比,反而多了额外的状态更新步骤。
内容的提问来源于stack exchange,提问作者Evok
相关产品推荐
相关产品推荐

