React性能优化实战:从卡顿到丝滑的8个技巧

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就回跳过渲染,直接复用上一次的渲染结果

  • 原理:核心是 浅比较

  • 内部逻辑大致如下

    js 复制代码
    function 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是一个新对象(引用地址变了),浅比较认为它"不同了"
  • 如何解决上面的问题?

    • 方法一:自定义比较函数
    js 复制代码
    const Child = React.memo(
        ({user})=>{
           console.log("Child render");
           return <div>{user.name}</div>;
        },
        (prevProps,nextProps)=>prevProps.user.name === nextProps.user.name
    )
    • 方法二:使用useMemo/useCallback稳定引用
    js 复制代码
    const 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)。
相关推荐
维李设论几秒前
前端智能化 | AG-UI实践及原理浅析
前端·aigc·agent
第七种黄昏1 分钟前
Vue3 中的 ref、模板引用和 defineExpose 详解
前端·javascript·vue.js
一只卡比兽2 分钟前
动态规划与贪心算法详解:原理、对比与代码实践
前端
aiwery5 分钟前
一文掌握 TypeScript 工具类型:Record、Partial、Omit、Pick 等实战用法
前端·代码规范
ankleless19 分钟前
C语言(12)——进阶函数
前端·html
一条上岸小咸鱼22 分钟前
Kotlin 基本数据类型(四):String
android·前端·kotlin
我是哈哈hh37 分钟前
【Node.js】ECMAScript标准 以及 npm安装
开发语言·前端·javascript·node.js
张元清1 小时前
电商 Feeds 流缓存策略:Temu vs 拼多多的技术选择
前端·javascript·面试
一枚前端小能手1 小时前
🎨 CSS布局从入门到放弃?Grid让你重新爱上布局
前端·css
晴空雨1 小时前
React 合成事件原理:从事件委托到 React 17 的重大改进
前端·react.js