Day 9:组件渲染优化
核心概念
让 React "少干活",页面更快!
| 问题 | 解决方案 |
|---|---|
| props 没变也要重渲染 | 用 React.memo |
| 计算结果重复算 | 用 useMemo |
| 函数重复创建 | 用 useCallback |
| 状态变化导致全量更新 | 用 useState 拆分 |
React.memo
跳过渲染,浅比较 props
// 简单用法
const Button = memo(function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
});
// 自定义比较
const Button = memo(
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
},
(prevProps, nextProps) => {
// 返回 true 表示相同,不重渲染
return prevProps.onClick === nextProps.onClick;
}
);
原理:
function memo(Component, arePropsEqual) {
return function MemoizedComponent(props) {
const previousProps = useRef(null);
if (arePropsEqual(previousProps.current, props)) {
// 相等,返回上次结果,不重渲染
return previousProps.result;
}
// 不相等,重新渲染
previousProps.current = props;
previousProps.result = <Component {...props} />;
return previousProps.result;
};
}
useMemo
缓存计算结果
function ExpensiveComponent({ list, filter }) {
const filteredList = useMemo(() => {
return list.filter(item => item.name.includes(filter));
}, [list, filter]);
return <ul>{filteredList.map(...)}</ul>;
}
useCallback
缓存函数引用
function Parent() {
const [count, setCount] = useState(0);
// 用 useCallback:函数引用保持不变
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
// 只有 count 变化时重建
const handleClickWithCount = useCallback(() => {
setCount(count + 1);
}, [count]);
return <Child onClick={handleClick} />;
}
虚拟列表
长列表优化,只渲染可见区域
import { FixedSizeList } from 'react-window';
function LongList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>{items[index].name}</div>
);
return (
<FixedSizeList
height={500}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}
核心思路:
传统方式:渲染 10000 条 → 创建 10000 个 DOM 节点 → 浏览器卡死
虚拟列表:只渲染可见的 20 条 → 只创建 20 个 DOM 节点 → 滚动时动态替换
代码分割
懒加载
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}
常见错误
// ❌ 错误:过度优化
const Button = memo(function Button({ text }) {
return <button>{text}</button>;
});
// text 是字符串,基本不会变,memo 没用
// ❌ 错误:useMemo 里面有副作用
const value = useMemo(() => {
fetchData(); // ❌ 副作用应该用 useEffect
}, []);
// ❌ 错误:useCallback 依赖过多
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]); // count 变化就重建,没意义
面试高频问题
| 问题 | 答案要点 |
|---|---|
| React.memo 原理? | 浅比较 props,决定是否跳过渲染 |
| useMemo vs useCallback? | useMemo 缓存值,useCallback 缓存函数 |
| 什么时候不用 memo? | props 频繁变化、组件简单 |
| 长列表��么优化? | 虚拟列表(react-window) |
自测答案
1. React.memo 的原理是什么?
- 浅比较新旧 props
- 相等 → 返回缓存,跳过渲染
- 不相等 → 重新调用组件
2. useMemo 和 useCallback 的区别?
| API | 作用 | 返回值 |
|---|---|---|
| useMemo | 缓存计算结果 | 任意值 |
| useCallback | 缓存函数引用 | 函数 |
3. 什么时候应该使用 React.memo?
- 适用:组件接收简单 props、渲染昂贵、不经常变化
- 不适用:props 频繁变化、组件非常简单
4. 长列表优化的核心思路是什么?
- 核心:只渲染可见区域(虚拟列表)
- 库:react-window、react-virtualized