useMemo与React.memo的区别、适用场景及示例问询
关于useMemo与React.memo的区别及适用场景
核心区别
两者都是React性能优化的工具,但作用对象和机制完全不同:
- React.memo是高阶组件,作用于整个组件,通过对比组件props的变化来决定是否复用之前的渲染结果,避免不必要的组件重渲染。
- useMemo是React Hook,作用于组件内部的计算值,通过依赖数组控制是否重新执行计算逻辑,缓存复杂计算的结果,避免每次渲染重复消耗算力。
另外,触发缓存更新的条件也不同:
- React.memo默认做props的浅对比,也可以传入自定义对比函数;只有当props变化时才会重新渲染组件。
- useMemo仅在依赖数组中的值发生变化时,才会重新执行传入的计算函数,否则直接返回缓存值。
React.memo 适用场景及示例
1. 纯展示型子组件,props极少变化
当子组件只负责展示数据,且接收的props不会随父组件的其他状态变化时,用React.memo包裹可以避免父组件重渲染时子组件跟着无效更新。
// 被React.memo包裹的用户卡片组件 const UserCard = React.memo(({ user }) => { console.log('UserCard 触发渲染'); return ( <div className="card"> <h2>{user.nickname}</h2> <p>{user.bio}</p> </div> ); }); // 父组件:自身状态频繁变化,但传给UserCard的props稳定 function ProfilePage() { const [tabIndex, setTabIndex] = useState(0); // 用useMemo保证user引用稳定,配合React.memo生效 const userInfo = useMemo(() => ({ nickname: '前端老鸟', bio: '专注React性能优化' }), []); return ( <div> <div className="tabs"> <button onClick={() => setTabIndex(0)}>首页</button> <button onClick={() => setTabIndex(1)}>动态</button> </div> {/* 切换tab时,UserCard不会重复渲染 */} <UserCard user={userInfo} /> </div> ); }
2. 长列表中的列表项组件
当渲染包含大量项的列表时,个别项数据变化时,React.memo可以保证只有变化的项重新渲染,而非整个列表全量更新。
// 列表项组件 const TodoItem = React.memo(({ todo, onToggle }) => { console.log(`渲染待办项:${todo.id}`); return ( <div onClick={() => onToggle(todo.id)}> <input type="checkbox" checked={todo.done} /> <span>{todo.content}</span> </div> ); }); // 列表父组件 function TodoList({ todos, onToggleTodo }) { return ( <div className="todo-list"> {todos.map(todo => ( <TodoItem key={todo.id} todo={todo} onToggle={onToggleTodo} /> ))} </div> ); }
当某个待办项的完成状态改变时,只有对应的TodoItem会重新渲染,其他项复用缓存的渲染结果,大幅提升长列表性能。
3. 被频繁重渲染的父组件所包含的子组件
如果父组件因为实时输入、动画等场景频繁重渲染,但子组件的props并不受这些变化影响,用React.memo包裹子组件可以切断无效的渲染传递。
useMemo 适用场景
1. 避免重复执行复杂计算
当组件中存在耗时的计算逻辑(如大数组筛选、排序、统计),且计算结果仅依赖特定变量时,用useMemo缓存计算结果,避免每次渲染都重复消耗资源。
function OrderStatistics({ orders, dateRange }) { // 仅当orders或dateRange变化时,重新计算统计数据 const stats = useMemo(() => { console.log('执行订单统计计算'); return { totalAmount: orders.filter(o => o.date >= dateRange.start && o.date <= dateRange.end) .reduce((sum, o) => sum + o.amount, 0), completedCount: orders.filter(o => o.status === 'completed').length }; }, [orders, dateRange]); return ( <div className="stats"> <p>总金额:{stats.totalAmount}</p> <p>完成订单数:{stats.completedCount}</p> </div> ); }
2. 保持引用稳定,配合React.memo生效
当需要将对象、数组或函数作为props传给被React.memo包裹的子组件时,用useMemo缓存这些值,避免每次渲染创建新引用导致子组件误触发重渲染。
function ParentComponent() { const [count, setCount] = useState(0); // 缓存配置对象,保持引用不变 const tableConfig = useMemo(() => ({ columns: ['姓名', '年龄', '职位'], pageSize: 10 }), []); return ( <div> <button onClick={() => setCount(count + 1)}>点击次数:{count}</button> {/* 点击按钮时,Table不会因为tableConfig引用变化而重渲染 */} <DataTable config={tableConfig} /> </div> ); }
3. 缓存自定义Hook的复杂返回值
如果自定义Hook返回的是对象或数组这类引用类型值,用useMemo缓存可以保证返回值的引用稳定,避免使用该Hook的组件因为引用变化而重渲染。
function useChartData() { const [timeRange, setTimeRange] = useState('week'); // 缓存图表数据对象 const chartData = useMemo(() => { // 模拟根据时间范围生成图表数据的复杂逻辑 return generateChartData(timeRange); }, [timeRange]); return { chartData, setTimeRange }; }
内容的提问来源于stack exchange,提问作者Akshan
相关产品推荐
相关产品推荐

