请教:我对React记忆化(memoization)相关API的理解是否正确?
我查阅了React中记忆化技术的相关文档,自认为已经理解了它们的用法。但我在一些项目中看到useMemo和useCallback的应用方式有所不同,想确认自己是否存在理解疏漏,以下是我的理解,请帮忙判断是否正确,若有误请予以纠正:
该Hook用于在组件重渲染之间缓存某个计算的结果。
const cachedValue = useMemo(() => calculateValue(), dependencies)
首次执行后,cachedValue会被存储在内存中;当依赖项不变时,后续渲染会直接返回该缓存值,无需重新计算。cachedValue对象的引用保持一致,在Object.is()判断中会返回true。
缓存耗时计算的结果能提升性能,因为即使组件重渲染也无需重复计算,但这需要付出运行Hook本身的代价。(对此我不确定)
此外,当将这些值作为props传递给经React.memo(Component)包装的组件,或作为useEffect的依赖项时,useMemo可避免不必要的重渲染。
该Hook用于在组件重渲染之间缓存函数定义(而非函数执行的结果)。
const cachedHandler = useCallback(() => { doSomething(); }, dependencies);
首次执行时,函数会被定义并保存(但不会被调用)到变量中;当依赖项不变时,后续渲染会返回同一个函数定义。
由于使用该Hook时不会调用函数,也不会缓存结果,因此不会带来即时的性能提升。(对此我不确定)
其优势仅体现在将缓存后的函数作为props传递给经React.memo(Component)包装的组件,或作为useEffect的依赖项时。
若将缓存后的函数通过内联函数传递为props(如<Component onSomething={() => cachedHandler()} />),则无法获得性能提升,因为每次都会创建新的匿名函数。(我见过几次这种用法)
经React.memo包装的组件会被缓存,只要其props保持不变,就会跳过重渲染。无需对原始值(如字符串)进行记忆化,但对于对象和函数,需使用useMemo或useCallback Hook。
许多项目和开发者会过早地用这些Hook包装值和函数,要么是出于惯例,要么是作为预防措施(“如果我们对所有内容都进行记忆化,未来就无需再考虑,且代价不高”)。
React官方文档及开发者建议,除非有必要(能带来可衡量的性能提升),否则不要使用这些Hook,因为使用它们并非无成本,还会增加认知复杂度(代码更难阅读和理解)。
以上描述并未涵盖所有场景,但适用于大多数简单情况。
理解正确性判断及补充
useMemo的运行成本:你的判断完全正确。useMemo在每次渲染时都要检查依赖项是否变化,同时还要维护缓存的内存空间。如果计算逻辑本身很简单(比如基础数值运算),使用useMemo的开销可能比重复计算还要高,这种情况下没必要使用。
useCallback的即时性能影响:这个理解也正确。useCallback仅负责缓存函数引用,不会直接带来性能提升,只有当缓存的函数被用作
useEffect/useMemo的依赖,或者传递给被React.memo包裹的子组件时,才能避免不必要的重渲染或重复执行,发挥其价值。内联函数传递缓存函数的问题:你提到的用法确实无效,每次渲染都会生成新的匿名函数,导致子组件接收的props引用变化,
React.memo的浅比较失效,子组件依然会重渲染。正确做法是直接传递缓存后的函数:<Component onSomething={cachedHandler} />。React.memo的补充:
React.memo默认对props做浅比较,如果props包含嵌套对象,浅比较无法识别内部属性的变化。若需要基于深层属性判断是否重渲染,可以给React.memo传入第二个参数(自定义比较函数),但这种场景不多见,且要注意自定义比较函数本身的性能开销。
内容的提问来源于stack exchange,提问作者Max

