1.渲染优化
1.1 合理使用useState
diff
- React默认情况下 父组件更新,子组件就会跟着重新渲染
- 重点: 如果子组件依赖的props没有变,可以使用React.memo 缓存组件,避免不必要的渲染。
js
import React from "react";
const Child = React.memo(function Child({ name }) {
console.log("Child render");
return <div>{name}</div>;
});
export default function App() {
const [count, setCount] = React.useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child name="Alice" />
</div>
);
}
// 适合场景:依赖props且props不经常改变
扩展说下 React.memo
-
React.memo 是一个高阶组件,他会对 函数组件进行"记忆化处理"。当父组件重新渲染时,如果传入的props没有变化,React就回跳过渲染,直接复用上一次的渲染结果
-
原理:核心是 浅比较
-
内部逻辑大致如下
jsfunction memo(Component, areEqual) { return function MemoizedComponent(props) { // 比较新旧 props const shouldRender = !areEqual ? !shallowEqual(prevProps, props) // 默认浅比较 : !areEqual(prevProps, props); // 用户自定义比较 if (shouldRender) { // 重新渲染 const result = Component(props); prevProps = props; prevResult = result; return result; } else { // 复用之前的渲染结果 return prevResult; } } }
- shallowEqual : 对对象的第一层属性进行 === 判断
- areEqual : 用户可以传入一个函数,自定义比较逻辑
-
浅比较的问题:默认只会检查props的第一层,不会做深比较
js
const Child = React.memo(({ user }) => {
console.log("Child render");
return <div>{user.name}</div>;
});
export default function App() {
const [count, setCount] = React.useState(0);
const user = { name: "Alice" };
return (
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child user={user} />
</div>
);
}
// 这里每次渲染都会触发 Child render,因为user是一个新对象(引用地址变了),浅比较认为它"不同了"
-
如何解决上面的问题?
- 方法一:自定义比较函数
jsconst Child = React.memo( ({user})=>{ console.log("Child render"); return <div>{user.name}</div>; }, (prevProps,nextProps)=>prevProps.user.name === nextProps.user.name )
- 方法二:使用useMemo/useCallback稳定引用
jsconst user = React.useMemo(() => ({ name: "Alice" }), [])
1.2 使用useCallback 和 useMemo 缓存引用
- React 通过浅比较来判断props是否变化。如果传递的是函数或者对象,很容易造成子组件重复渲染
js
const handleClick = useCallback(()=>{
console.log('点击了')
},[])
const list = useMemo(() => [1, 2, 3], []);
// 避免了"每次渲染都创建新函数/对象" 的问题
1.3 合理拆分组件
- 把大组件拆分成小组件,减少每次渲染需要更新的范围。
- 配合
React.memo
,只更新有变化的部分。
2. 状态管理优化
2.1 状态下沉
- 尽量把state放在需要用到的最小组件层级,避免因为全局状态更新而导致大面积重新渲染
2.2 useReducer 代替复杂 useState
- 当 state 逻辑复杂时,用
useReducer
管理更清晰,减少重复更新。
2.3 外部状态管理
- 对于大型项目,可以用 Redux Toolkit、Zustand 或 Jotai 这类库,把状态拆分更细,按需订阅,减少渲染。
3. 代码分割 和 懒加载
3.1 路由级别懒加载
- 利用React.lazy 和 Suspense
js
const About = React.lazy(()=>import('./About'))
<Suspense fallback={<div>Loading...</div>}>
<About />
</Suspense>
// 只有在用户需要时加载对应页面,减少首屏体积
3.2 按需加载组件 & 第三方库
- 用
lodash-es
替代lodash
,支持 Tree Shaking。 - 图表库(如 ECharts、Chart.js)按需引入模块。
4. 列表优化
4.1 虚拟列表(Virtualized List)
- 当列表渲染数据过多(上千条)时,可以使用react-window 或者 react-virtualized ,只渲染可视区域。
js
import {FixedSizeList as List} from 'react-window';
<List
height={400}
itemCount={1000}
itemSize={35}
width={300}
>
{({ index, style }) => <div style={style}>Row {index}</div>}
</List>
4.2 key的使用
- 列表中的
key
要保证唯一且稳定,避免 React 误判导致不必要的重新渲染。
5. 图片与静态资源优化
- 使用 图片懒加载 (
loading="lazy"
或 react-lazyload)。 - 图片压缩(WebP、AVIF)。
- CDN 加速静态资源。
6. 并发特性(React 18+)
6.1 自动批处理(Automatic Batching)
多个 setState
会合并在一次渲染中执行,提高性能。
6.2 useTransition
- 适合输入搜索这种场景,保证交互流畅
js
const [isPending,startTransition] = useTransition();
startTransition(()=>{
setFilteredData(expensiveFilter(data));
})
扩展下useTransition
- react18引入的并发特性
- 作用时:把不紧急的更新标记为 "过渡"
- 好处:React调度时会优先处理紧急更新(比如输入框输入) , 延后处理 "过渡更新"(比如大列表渲染),从而避免卡顿
js
import React, {useState,useTransition} from 'react';
export default function App() {
const [query,setQuery] = useState('');
const [list, setList] = useState([]);
const [isPending,startTransition] = useTransition()
const handleChange = (e)=>{
const value = e.target.value;
setQuery(value); // 输入框输入->比较紧急
// 大列表更新->比较不紧急,放到过渡中
startTransition(()=>{
const newList = Array.from({ length: 5000 }, (_, i) => value + i);
setList(newList);
})
}
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <p>Loading...</p>}
<ul>
{list.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
-
输入时:
setQuery(value)
是 紧急更新,会立即更新输入框,不会卡顿。setList(newList)
被包裹在startTransition
里,React 会把它当作低优先级任务,在空闲时渲染。isPending
表示是否有过渡更新正在进行,可以用来显示 loading。
-
和
useDeferredValue
的区别useTransition
:用于包裹更新逻辑,把某个状态更新标记为过渡。useDeferredValue
:用于延迟使用某个值,让它的"消费者"低优先级更新。
👉 举例:
useTransition
:你控制"什么时候触发过渡"。useDeferredValue
:React 自动帮你"延迟值的传递"。
6.3 useDeferredValue
- React 18 新增的并发特性 Hook。
- 作用:让某个值的使用变得延迟 ,从而把依赖这个值的更新降级为 "过渡更新"
- 好处:避免因为某个值的变化导致耗时渲染任务阻塞"紧急更新"。
js
import React,{useState,useDeferredValue} from 'react';
export default function App() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query); // 延迟版本的query
const list = Array.from({ length: 5000 }, (_, i) => deferredQuery + i);
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ul>
{list.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
// 输入框的值 原始版本query紧急更新
// 列表使用的是 `deferredQuery`,它会"延迟"更新 → React 先保证输入框不卡,再去慢慢更新大列表。
7. 构建优化
- 开启 生产模式 (
NODE_ENV=production
)。 - 使用 Webpack/Vite 的 代码压缩 和 Tree Shaking。
- 按需加载 CSS(CSS Modules / Tailwind JIT)。
8. 性能分析工具
- React DevTools Profiler:分析组件渲染性能。
- Lighthouse:检测页面性能与优化建议。
- Web Vitals:核心性能指标(LCP、CLS、FID)。