Day 9:组件渲染优化

1 阅读2分钟

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