React 渲染性能终极指南memo、useMemo 与 useCallback 的正确使用时机一、React 的渲染触发器React 组件的重渲染由三个条件触发state 变化、props 变化、父组件渲染。第三个是最容易忽略的——即使 props 没有实际变化只要父组件渲染了子组件默认也会渲染。function Parent() { const [count, setCount] useState(0); return ( div button onClick{() setCount(c c 1)}1/button ExpensiveChild / {/* count 变化 → Parent 渲染 → ExpensiveChild 也渲染 */} /div ); }默认情况下当组件状态发生变化时不仅当前组件会重新渲染其所有的子组件和孙子组件也会随之触发渲染。为了优化这一过程React.memo通过浅比较 props 来决定是否跳过渲染若 props 未发生变化则跳过渲染若 props 发生变化则正常渲染。二、React.memo 的正确用法React.memo通过浅比较 props 来跳过不必要的重渲染const ExpensiveChild React.memo(function ExpensiveChild({ data, onSelect }) { // 复杂渲染逻辑 return div{/* ... */}/div; });// 注意如果 onSelect 每次都是新函数memo 会失效function Parent() {const [items, setItems] useState([]);// 问题每次渲染都创建新函数memo 的浅比较总是 falseconst handleSelect (id) setItems(prev prev.filter(i i.id ! id));return ;}React.memo 的适用条件 | 条件 | 判断方法 | |------|----------| | 组件渲染开销大 | React DevTools Profiler 中渲染时间 5ms | | Props 变化频率低 | 大部分渲染中 props 不变 | | Props 是原始值或稳定引用 | 没有每次渲染都重新创建的函数/对象 | ## 三、useMemo缓存计算结果 useMemo 只在依赖变化时重新计算。适用场景 jsx function ProductList({ products, filter }) { // 问题每次渲染都执行 filter sort即使 products 和 filter 没变 const filtered products .filter(p p.category filter) .sort((a, b) b.rating - a.rating); // 修复仅在依赖变化时重新计算 const filtered useMemo(() { return products .filter(p p.category filter) .sort((a, b) b.rating - a.rating); }, [products, filter]); return List items{filtered} /; }useMemo 的滥用陷阱——不要为简单计算加缓存// 反例简单字符串拼接不需要 useMemo const fullName useMemo(() ${firstName} ${lastName}, [firstName, lastName]); // 反例useMemo 自身有开销依赖数组对比、函数创建 // 对于 O(1) 的计算useMemo 的开销可能超过收益 const doubled useMemo(() count * 2, [count]); // 直接写 count * 2 更好四、useCallback稳定函数引用useCallback返回 memoized 的函数引用只有当依赖变化时才创建新函数。它的唯一价值是配合 React.memo使用。function SearchPage() { const [query, setQuery] useState(); const [results, setResults] useState([]); // 修复前每次渲染都创建新函数 // const handleSearch () fetchResults(query); // 修复后query 不变时返回同一个函数引用 const handleSearch useCallback(() { fetchResults(query); }, [query]); return ( div SearchInput value{query} onChange{setQuery} / {/* SearchButton 使用 React.memohandleSearch 引用稳定才能跳过渲染 */} SearchButton onSearch{handleSearch} / ResultList items{results} / /div ); } const SearchButton React.memo(({ onSearch }) { console.log(SearchButton rendered); return button onClick{onSearch}搜索/button; });使用决策树是否需要 useCallback? ├─ 函数传递给使用 React.memo 的子组件→ 是 → 使用 ├─ 函数作为 useEffect 的依赖→ 是 → 使用 ├─ 函数在大量列表项的 props 中传递→ 是 → 使用 └─ 其他情况 → 不需要五、总结React 性能优化的三个 API 有各自的使用边界React.memo用于跳过子树渲染适合渲染开销大、props 变化少的叶子组件useMemo用于缓存计算开销大的结果过滤、排序、聚合不适合简单计算useCallback的唯一作用是稳定函数引用以配合React.memo否则是纯粹的开销。核心原则先测量再优化。用 React DevTools Profiler 找出真正的慢组件而非在所有地方无差别加 memo。