React.memo有什么作用以及如何使用
前言
React.memo() 是一个高阶组件(HOC),用于优化函数组件的性能。它通过记忆组件渲染结果的方式,在组件接收相同的 props 时跳过渲染操作。
1. 基本使用方式
import React, { useState } from 'react';
// 子组件
const ExpensiveComponent = React.memo(({ title, count }) => {
console.log('ExpensiveComponent rendered!');
return (
<div>
<h2>{title}</h2>
<p>Count: {count}</p>
</div>
);
});
// 父组件
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
return (
<div>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type something..."
/>
<button onClick={() => setCount(c => c + 1)}>
Increment Count
</button>
<ExpensiveComponent
title="Memo Demo"
count={count}
/>
</div>
);
}
在这个例子中:
ExpensiveComponent
被React.memo
包裹- 只有当
title
或count
改变时,ExpensiveComponent
才会重新渲染
当父组件中的 text 状态改变时,ExpensiveComponent 不会重新渲染
2. 使用自定义比较函数
import React, { useState } from 'react';
// 自定义比较函数
const areEqual = (prevProps, nextProps) => {
// 只有当数组长度发生变化时才重新渲染
return prevProps.items.length === nextProps.items.length;
};
const ItemsList = React.memo(({ items }) => {
console.log('ItemsList rendered!');
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}, areEqual);
function ListContainer() {
const [items, setItems] = useState(['Item 1', 'Item 2']);
const [counter, setCounter] = useState(0);
const updateLastItem = () => {
setItems(prev => {
const newItems = [...prev];
newItems[newItems.length - 1] += ' updated';
return newItems;
});
};
return (
<div>
<button onClick={() => setCounter(c => c + 1)}>
Increment Counter: {counter}
</button>
<button onClick={updateLastItem}>Update Last Item</button>
<ItemsList items={items} />
</div>
);
}
这个例子展示了:
- 如何使用自定义比较函数
- 只关注特定的属性变化
- 可以实现更精细的渲染控制
3. 处理对象和函数 props
import React, { useState, useCallback, useMemo } from 'react';
const UserProfile = React.memo(({ user, onUpdate }) => {
console.log('UserProfile rendered!');
return (
<div>
<h3>{user.name}</h3>
<p>Age: {user.age}</p>
<button onClick={onUpdate}>Update Age</button>
</div>
);
});
function UserContainer() {
const [user, setUser] = useState({ name: 'John', age: 25 });
const [theme, setTheme] = useState('light');
// 使用 useCallback 记忆函数
const handleUpdate = useCallback(() => {
setUser(prev => ({
...prev,
age: prev.age + 1
}));
}, []); // 空依赖数组,函数永远不会改变
// 使用 useMemo 记忆对象
const memoizedUser = useMemo(() => user, [user.name, user.age]);
return (
<div>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Toggle Theme: {theme}
</button>
<UserProfile
user={memoizedUser}
onUpdate={handleUpdate}
/>
</div>
);
}
这个例子说明:
- 如何正确处理对象类型的
props
- 使用
useCallback
处理函数props
- 使用
useMemo
处理对象props
- 避免不必要的重新渲染
4. 条件性能优化
import React, { useState } from 'react';
// 创建一个计数器来跟踪渲染次数
const withRenderCount = (WrappedComponent) => {
return function WithRenderCount(props) {
const [renderCount, setRenderCount] = useState(0);
React.useEffect(() => {
setRenderCount(prev => prev + 1);
});
return (
<div>
<div>Render count: {renderCount}</div>
<WrappedComponent {...props} />
</div>
);
};
};
// 创建两个组件:一个使用 memo,一个不使用
const RegularComponent = withRenderCount(({ value }) => {
return <div>Regular: {value}</div>;
});
const MemoizedComponent = withRenderCount(React.memo(({ value }) => {
return <div>Memoized: {value}</div>;
}));
function ComparisonContainer() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
return (
<div>
<div>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type to trigger parent update"
/>
<button onClick={() => setCount(c => c + 1)}>
Increment Count
</button>
</div>
<RegularComponent value={count} />
<MemoizedComponent value={count} />
</div>
);
}
这个例子展示了:
- 使用和不使用
memo
的性能差异 - 通过渲染计数直观展示优化效果
- 何时使用
memo
是有意义的
5. 注意事项和最佳实践
import React, { useState, useCallback } from 'react';
// ❌ 不恰当的使用方式
const BadExample = React.memo(({ items, onItemClick }) => {
return (
<ul>
{items.map((item, index) => (
<li key={index} onClick={() => onItemClick(item)}>
{item}
</li>
))}
</ul>
);
});
// ✅ 正确的使用方式
const GoodExample = React.memo(({ items, onItemClick }) => {
return (
<ul>
{items.map((item, index) => (
<ListItem
key={item.id}
item={item}
onClick={onItemClick}
/>
))}
</ul>
);
});
// 分离的列表项组件
const ListItem = React.memo(({ item, onClick }) => {
return (
<li onClick={() => onClick(item)}>
{item.text}
</li>
);
});
function Container() {
const [items, setItems] = useState([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' }
]);
const handleItemClick = useCallback((item) => {
console.log('Clicked:', item);
}, []);
return (
<div>
<GoodExample
items={items}
onItemClick={handleItemClick}
/>
</div>
);
}
最佳实践总结:
- 何时使用
React.memo
:
- 组件经常重新渲染
- 组件接收相同
props
时渲染输出不变 - 组件包含复杂的计算逻辑
- 何时不使用
React.memo
:
- 组件很简单,渲染成本低
props
经常变化- 组件需要频繁更新
3. 优化建议:
- 配合
useCallback
和useMemo
使用 - 合理设计组件的
props
结构 - 避免传递内联对象和函数
- 使用不可变数据结构
- 适当拆分组件
React.memo
是一个强大的性能优化工具,但需要谨慎使用。过度优化可能会适得其反,增加代码复杂度而没有明显的性能提升。建议在实际遇到性能问题时再考虑使用 React.memo
。
下一篇:无