React性能优化:从“卡成狗”到“丝般顺滑”的5个秘诀

前言

React已经很快了,但如果你不注意细节,它会做很多"无用功":组件没必要的重渲染、大列表全量渲染、状态更新导致整个页面刷新......这些问题累积起来,再好的电脑也扛不住。

今天我们不聊虚拟DOM原理,直接上代码、上工具,告诉你哪些写法是"性能杀手",哪些是"救星"。优化完,你的React应用会快得让用户怀疑是不是装了外挂。

一、第1招:用React.memo避免"父动子也动"

React默认:父组件更新,所有子组件都会重新渲染(即使props没变)。这会导致大量浪费。

差代码

jsx 复制代码
const Child = ({ name }) => {
  console.log('Child渲染了');
  return <div>{name}</div>;
};

const Parent = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(c => c+1)}>点击 {count}</button>
      <Child name="张三" />
    </div>
  );
};

每次点击按钮,Child都会重新渲染,但它的name根本没变。

好代码 :用React.memo包装子组件。

jsx 复制代码
const Child = React.memo(({ name }) => {
  console.log('Child渲染了');
  return <div>{name}</div>;
});

现在只有name变化时,Child才会重新渲染。

注意 :如果Child接收的props包含函数或对象,需要配合useCallbackuseMemo(见第2招)。

二、第2招:用useCallbackuseMemo缓存函数和值

在React组件里,每次渲染都会重新创建所有内联函数和对象。即使内容相同,引用也不同,导致React.memo失效。

差代码

jsx 复制代码
const Parent = () => {
  const handleClick = () => console.log('clicked'); // 每次渲染都是新函数
  return <Child onClick={handleClick} />;
};

好代码 :用useCallback缓存函数。

jsx 复制代码
const Parent = () => {
  const handleClick = useCallback(() => console.log('clicked'), []); // 依赖为空,永远不变
  return <Child onClick={handleClick} />;
};

对于复杂计算的值(比如过滤大列表),用useMemo缓存结果:

jsx 复制代码
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);

三、第3招:虚拟列表,渲染一万条也不怕

直接渲染长列表(比如聊天记录、商品列表)会导致浏览器创建上万个DOM节点,内存爆炸,滚动卡顿。虚拟列表只渲染可视区域内的几条,滚动时动态替换。

不用自己造轮子,用现成库

  • react-window(轻量)
  • react-virtualized(功能全)
jsx 复制代码
import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => (
  <div style={style}>行 {index}</div>
);

<List
  height={400}
  itemCount={10000}
  itemSize={35}
  width={300}
>
  {Row}
</List>

一秒渲染一万条,滚动丝滑。

四、第4招:代码分割 + 懒加载,别一次加载所有组件

你的用户访问首页,结果你把后台管理、用户设置、订单详情所有页面的代码都下载了。浪费流量,也拖慢首屏。

React.lazy + Suspense

jsx 复制代码
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

每个路由的代码单独打包,只有访问时才加载。

五、第5招:避免内联对象和函数传递

即使不用memo,内联对象也会导致子组件每次接收新对象,触发重渲染。

差代码

jsx 复制代码
<Child style={{ color: 'red' }} />  // 每次渲染都是新对象

好代码 :把对象提取到组件外部或使用useMemo

jsx 复制代码
const childStyle = { color: 'red' }; // 外部定义,引用不变
<Child style={childStyle} />

六、额外绝招:使用useTransition标记非紧急更新

React 18引入了useTransition,可以把某些更新标记为"低优先级",让高优先级交互(如输入框打字)更流畅。

jsx 复制代码
const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const [list, setList] = useState([]);

const handleChange = (e) => {
  const value = e.target.value;
  setQuery(value); // 紧急更新:更新输入框
  startTransition(() => {
    // 非紧急更新:过滤大列表
    const filtered = hugeList.filter(item => item.includes(value));
    setList(filtered);
  });
};

这样打字不会卡,列表过滤稍后完成。

七、工具检测:React DevTools Profiler

安装React DevTools,打开Profiler标签,录制一段操作,可以看到每个组件渲染耗时。颜色越黄/红,越需要优化。

八、总结:优化口诀

  • 子组件纯展示,包上React.memo
  • 函数和对象,用useCallbackuseMemo缓存。
  • 长列表用虚拟滚动。
  • 路由懒加载,按需取。
  • 内联对象移出去,引用不变。
  • 紧急更新与非紧急分开,用useTransition

优化完,你的React应用会快得飞起。用户会惊叹:"这网站怎么比原生App还流畅?"

相关推荐
米丘2 小时前
Vue 3.x 单文件组件(SFC)模板编译过程解析
前端·vue.js·编译原理
helloweilei2 小时前
Web Streams 简介
前端·javascript
悟空瞎说2 小时前
Flutter热更新 Shorebird CodePush 原理、实现细节及费用说明
前端·flutter
didadida2622 小时前
从“不存在”的重复请求,聊到 Web 存储的深坑
前端
xiaominlaopodaren2 小时前
Three.js 渲染原理-透明渲染为什么这么难
前端
米丘2 小时前
vue3.x 内置指令有哪些?
前端·vue.js
米丘2 小时前
Vue 3.x 模板编译优化:静态提升、预字符串化与 Block Tree
前端·vue.js·编译原理
We་ct2 小时前
HTML5 原生拖拽 API 基础原理与核心机制
前端·javascript·html·api·html5·浏览器·拖拽
是上好佳佳佳呀2 小时前
【前端(八)】CSS3 属性值笔记:渐变、自定义字体与字体图标
前端·笔记·css3