前端工程Technical Deep Dive

React 性能优化实战 —— 避免重渲染的 15+ 种方案

发布时间2026/03/29
分类前端工程
预计阅读2 分钟
作者吴长龙
*

性能问题的根源

01.内容

性能问题的根源

React 性能问题的核心是不必要的重渲染。每次状态变化,React 会从该组件开始向下遍历整棵 Fiber 树,找出需要更新的部分。理解这一点,才能有的放矢地进行优化。

一、组件级优化

#### 1. React.memo - 缓存组件

jsx snippetjsx
// 不使用 memo:父组件任何状态变化都会导致 Child 重渲染
function Parent() {
  const [count, setCount] = useState(0);
  return <Child />;
}

// 使用 memo:只有 props 变化才重渲染
const Child = memo(function Child({ name }) {
  return <div>{name}</div>;
});

// 自定义比较函数
const Child = memo(function Child({ name }) {
  return <div>{name}</div>;
}, (prevProps, nextProps) => {
  // 返回 true 表示相等,不重渲染
  return prevProps.name === nextProps.name;
});

注意:默认使用浅比较,如果 props 是对象,需要注意引用变化。

#### 2. useMemo - 缓存计算结果

jsx snippetjsx
function ExpensiveComponent({ list, filter }) {
  // 不优化:每次渲染都重新计算
  const filtered = list.filter(item => item.name.includes(filter));
  
  // 优化:只在依赖变化时重新计算
  const filtered = useMemo(
    () => list.filter(item => item.name.includes(filter)),
    [list, filter]
  );
  
  return <ul>{filtered.map(...)}</ul>;
}

#### 3. useCallback - 稳定函数引用

jsx snippetjsx
function Parent() {
  const [count, setCount] = useState(0);
  
  // 问题:每次渲染都是新函数,导致子组件重渲染
  const handleClick = () => console.log(count);
  
  // 解决:稳定引用
  const handleClick = useCallback(() => {
    console.log(count);
  }, [count]); // 或者 [] 如果不依赖 count
  
  return <Child onClick={handleClick} />;
}

二、状态优化

#### 4. 状态细化

jsx snippetjsx
// 问题:无关状态放在一起
function BadComponent() {
  const [state, setState] = useState({ 
    user: null, 
    loading: false 
  });
  
  // loading 变化会导致 user 相关组件重渲染
  return <UserProfile user={state.user} />;
}

// 解决:拆分成独立状态
function GoodComponent() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  
  return <UserProfile user={user} />;
}

#### 5. 状态提升的层次

jsx snippetjsx
// 问题:状态放得太高
function GrandParent() {
  const [count, setCount] = useState(0);
  return <Parent count={count} />;
}

function Parent({ count }) {
  return <Child count={count} />; // 每一层都要传
}

function Child({ count }) {
  return <div>{count}</div>;
}

// 解决:状态放到需要的地方
function Parent() {
  return <ChildWithOwnState />;
}

function ChildWithOwnState() {
  const [count, setCount] = useState(0);
  return <div onClick={() => setCount(c => c + 1)}>{count}</div>;
}

三、Context 优化

#### 6. 拆分 Context

jsx snippetjsx
// 问题:一个 Context 变化,所有消费者都重渲染
const AppContext = createContext();

function App() {
  return (
    <AppContext.Provider value={{ user, theme, setTheme }}>
      <Toolbar />
      <Content />
    </AppContext.Provider>
  );
}

// 解决:拆分成多个 Context
const UserContext = createContext();
const ThemeContext = createContext();

function App() {
  return (
    <UserContext.Provider value={user}>
      <ThemeContext.Provider value={{ theme, setTheme }}>
        <Toolbar />
        <Content />
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

#### 7. 动态 Context 值

jsx snippetjsx
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  // 问题:每次 theme 变化都创建新对象
  const value = { theme, setTheme };
  
  // 解决:使用 useMemo 缓存
  const value = useMemo(() => ({ theme, setTheme }), [theme]);
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

四、列表优化

#### 8. 虚拟列表

jsx snippetjsx
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>{items[index].name}</div>
  );
  
  return (
    <FixedSizeList
      height={400}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

#### 9. 列表 key 的艺术

jsx snippetjsx
// 问题:使用索引作为 key
function BadList({ items }) {
  return items.map((item, index) => (
    <Child key={index} item={item} />
  ));
}

// 解决:使用稳定 ID
function GoodList({ items }) {
  return items.map(item => (
    <Child key={item.id} item={item} />
  ));
}

五、渲染控制

#### 10. 条件渲染优化

jsx snippetjsx
// 问题:不必要的渲染
function Component() {
  const [show, setShow] = useState(false);
  
  return (
    <div>
      {show && <HeavyComponent />}
      <button onClick={() => setShow(!show)}>Toggle</button>
    </div>
  );
}

// 解决:使用懒加载
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function Component() {
  const [show, setShow] = useState(false);
  
  return (
    <div>
      {show && (
        <Suspense fallback={<Loading />}>
          <HeavyComponent />
        </Suspense>
      )}
      <button onClick={() => setShow(!show)}>Toggle</button>
    </div>
  );
}

#### 11. useTransition - 优先处理交互

jsx snippetjsx
function SearchResults() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  
  const handleChange = (e) => {
    const value = e.target.value;
    // 标记为低优先级,不阻塞输入
    startTransition(() => {
      setQuery(value);
    });
  };
  
  return (
    <div>
      <input onChange={handleChange} />
      {isPending && <Loading />}
      <Results query={query} />
    </div>
  );
}

六、Effect 优化

#### 12. 避免 Effect 中的昂贵操作

jsx snippetjsx
// 问题:Effect 中每次渲染都执行
useEffect(() => {
  const analytics = getAnalytics();
  analytics.track(event);
}, [event]);

// 解决:使用 useRef 限制频率
const tracked = useRef(new Set());

useEffect(() => {
  if (!tracked.current.has(event)) {
    tracked.current.add(event);
    const analytics = getAnalytics();
    analytics.track(event);
  }
}, [event]);

#### 13. 依赖项精确化

jsx snippetjsx
// 问题:依赖过多导致频繁执行
useEffect(() => {
  doSomething(a, b, c, d);
}, [a, b, c, d]);

// 解决:只依赖需要的值,或者使用 useCallback
const doSomething = useCallback((val) => {
  // 只使用 a
}, [a]);

useEffect(() => {
  doSomething(b);
}, [doSomething, b]);

七、开发者工具

#### 14. React DevTools Profiler

jsx snippetjsx
// 开启性能分析
import { Profiler } from 'react';

function onRender(id, phase, actualTime, baseTime, ...){
  console.log(`${id} ${phase}: ${actualTime}ms`);
}

<Profiler id="MyComponent" onRender={onRender}>
  <MyComponent />
</Profiler>

#### 15. why-did-you-render

javascript snippetjavascript
// 安装:npm install @welldone-software/why-did-you-render
import whyDidYouRender from '@welldone-software/why-did-you-render';

whyDidYouRender(React, {
  trackAll: true,
  trackHooks: true,
  logOwnerReasons: true,
});

性能优化检查清单

检查项优先级
使用 React.memo 包装纯展示组件
大列表使用虚拟滚动
useMemo/useCallback 缓存昂贵计算
拆分 Context
状态细化到最小范围
使用 useTransition 处理非紧急更新

总结

React 性能优化是一个系统工程:

  • 测量优先:先用 Profiler 找到瓶颈
  • 从大问题着手:解决 20% 的问题带来 80% 的提升
  • 避免过度优化:不是每个组件都需要 memo
  • 理解权衡:优化往往伴随复杂度增加

记住:可读、可维护的代码 > 过度优化的代码