2023-06-26 02:51:52
在JavaScript中实现useMemo的核心是通过缓存计算结果,并在依赖项未变化时直接返回缓存值,避免重复计算。 以下是具体实现逻辑、关键点及使用场景的详细说明:
一、简化版useMemo的实现逻辑通过闭包存储上一次的计算结果和依赖项,每次调用时比较当前依赖项与上一次的值,决定是否重新计算:
function createUseMemo() { let lastValue = null; let lastDependencies = []; return function useMemo(computeFunction, dependencies) { const dependenciesChanged = dependencies.some((dep, index) => dep !== lastDependencies[index]); if (dependenciesChanged || lastDependencies.length === 0) { lastValue = computeFunction(); lastDependencies = dependencies; } return lastValue; };}const myUseMemo = createUseMemo();避免重复计算在组件渲染或数据更新时,若依赖项未变化,直接返回缓存值,减少不必要的计算。例如:
对大型列表进行过滤、排序等耗时操作。
生成复杂数据结构(如图表数据、树形结构)。
防止引用变化导致的子组件渲染React中,若子组件接收的props是引用类型(如对象、数组),即使内容未变,引用地址变化也会触发重新渲染。useMemo可确保依赖不变时返回同一引用,优化子组件性能。
典型场景示例
function ExpensiveComponent({ items }) { const sortedItems = useMemo(() => [...items].sort((a, b) => a.id - b.id), [items]); return <List data={sortedItems} />;}仅当items变化时重新排序,避免每次渲染都执行排序操作。
精确控制重新计算时机
依赖项为空数组([])时,计算仅在组件首次渲染时执行一次,后续直接返回缓存值。
依赖项包含特定变量时,仅在这些变量变化时重新计算。
避免陈旧闭包问题若计算函数内部使用的变量未列入依赖项,会导致闭包捕获旧值,引发逻辑错误。例如:
function Counter({ initialCount }) { const [count, setCount] = useState(initialCount); const doubleCount = useMemo(() => count * 2, []); // 错误:缺少依赖项count // 正确写法:useMemo(() => count * 2, [count]);}依赖项缺失会导致doubleCount始终基于初始count值计算。
平衡优化与正确性
依赖项过少可能导致数据陈旧,过多则可能频繁触发计算。
最佳实践:将计算函数中所有外部变量(如props、state、其他hooks返回值)均列入依赖项。
核心区别
useMemo:缓存计算结果(值),适用于数据或复杂计算。const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallback:缓存函数定义,避免函数引用变化导致子组件渲染。const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);
选择依据
使用useMemo:需缓存数据结果(如过滤后的列表、计算后的对象)。
使用useCallback:需缓存函数引用(如传递给子组件的回调函数,配合React.memo优化性能)。
避免过度优化
简单计算(如a + b)无需使用useMemo,直接计算开销更低。
仅在计算成本高或依赖项频繁变化时使用。
依赖项的深度比较
简化版实现使用浅比较,对引用类型可能不够严谨。实际React中可能采用Object.is或深度比较工具(如lodash.isEqual)。
组件卸载时的清理
实际React实现会确保组件卸载时清理缓存,避免内存泄漏。
useMemo通过缓存计算结果和依赖项比较,显著优化了React应用的性能,尤其适用于高开销计算和引用稳定性场景。其核心在于依赖项数组的正确管理,需平衡优化效果与代码正确性。与useCallback配合使用,可进一步减少不必要的渲染和计算,构建高效的前端应用。