Back to Blog
By AriesZhou · · 3 min read

React Performance Optimization: Preventing Component Re-renders

React

In medium to large projects, a parent component’s state update often causes child components to re-render even when their props haven’t changed. These unnecessary renders accumulate and significantly affect performance. Mastering techniques to prevent re-renders is key to React performance optimization.

React’s re-rendering mechanism is central to performance. Understanding when and how to prevent unnecessary renders can effectively improve page responsiveness.

1. Re-render Triggers

React components re-render in the following cases:

  • state changes
  • props changes
  • Parent component re-renders
graph TD
    A[组件渲染] --> B{状态变化?}
    B -->|是| C[重新渲染]
    B -->|否| D{Props 变化?}
    D -->|是| C
    D -->|否| E{父组件渲染?}
    E -->|是| F{浅比较不同?}
    E -->|否| G[不重新渲染]
    F -->|是| C
    F -->|否| G

2. React.memo

2.1 Basic Usage

React.memo performs a shallow comparison for function components:

function ChildComponent({ name, onClick }) {
  console.log('Child 渲染了');
  return <button onClick={onClick}>{name}</button>;
}

// 只有 props 变化时才重新渲染
const MemoizedChild = React.memo(ChildComponent);

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>父组件计数: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>增加</button>
      {/* count 变化时,MemoizedChild 不会重新渲染 */}
      <MemoizedChild name="子组件" onClick={() => {}} />
    </div>
  );
}

2.2 Custom Comparison Function

function MyComponent({ data }) {
  return <div>{data.value}</div>;
}

// 自定义比较逻辑
const MemoizedComponent = React.memo(MyComponent, (prevProps, nextProps) => {
  return prevProps.data.id === nextProps.data.id;
});

3. useMemo

3.1 Memoizing Computed Values

function ExpensiveComponent({ list, filter }) {
  // 只有 list 或 filter 变化时才重新计算
  const filteredList = useMemo(() => {
    console.log('计算中...');
    return list.filter(item => item.name.includes(filter));
  }, [list, filter]);

  return (
    <ul>
      {filteredList.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

3.2 Avoiding Object Reference Changes

function Component({ a, b }) {
  // ❌ 每次渲染都创建新对象
  const config = { mode: 'dark', size: 'large' };

  // ✅ 使用 useMemo 缓存对象
  const config = useMemo(() => ({
    mode: 'dark',
    size: 'large',
  }), []);

  // ✅ 数组同理
  const items = useMemo(() => [a, b], [a, b]);
}

4. useCallback

4.1 Memoizing Callback Functions

function Parent() {
  const [count, setCount] = useState(0);

  // ❌ 每次渲染都创建新函数
  const handleClick = () => console.log('click');

  // ✅ 缓存回调函数
  const handleClick = useCallback(() => {
    console.log('click');
  }, []);

  // ✅ 带依赖的回调
  const handleIncrement = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <Child onClick={handleClick} onIncrement={handleIncrement} />;
}

4.2 Using with React.memo

function List({ items, onItemClick }) {
  console.log('List 渲染');
  return (
    <ul>
      {items.map(item => (
        <ListItem
          key={item.id}
          item={item}
          onClick={onItemClick}
        />
      ))}
    </ul>
  );
}

const MemoizedList = React.memo(List);

function App() {
  const [query, setQuery] = useState('');
  const [items] = useState([{ id: 1, name: '苹果' }, { id: 2, name: '香蕉' }]);

  const handleClick = useCallback((id) => {
    console.log('点击了', id);
  }, []);

  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <MemoizedList items={items} onItemClick={handleClick} />
    </div>
  );
}

5. Class Component Optimization

5.1 shouldComponentUpdate

class Counter extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 只在 count 变化时更新
    return nextProps.count !== this.props.count;
  }

  render() {
    return <div>计数: {this.props.count}</div>;
  }
}

5.2 PureComponent

class UserList extends React.PureComponent {
  render() {
    // 自动进行浅比较
    return (
      <ul>
        {this.props.users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    );
  }
}

6. Common Pitfalls

6.1 Inline Function Issues

function Component() {
  return (
    <div>
      {/* ❌ 每次渲染创建新函数 */}
      <Child onClick={() => handleClick(id)} />

      {/* ✅ 使用 useCallback */}
      <Child onClick={useCallback(() => handleClick(id), [id])} />
    </div>
  );
}

6.2 Array and Object Literals

function Component({ items }) {
  return (
    <div>
      {/* ❌ 新数组引用 */}
      <Child items={[...items, newItem]} />

      {/* ✅ 使用 useMemo */}
      <Child items={useMemo(() => [...items, newItem], [items, newItem])} />
    </div>
  );
}

7. Performance Optimization Checklist

Optimization methodUse casePriority
React.memoFunction componentsHigh
useMemoCache computed valuesHigh
useCallbackCache callback functionsHigh
shouldComponentUpdateClass componentsMedium
PureComponentClass componentsMedium

8. Summary

Preventing unnecessary re-renders:

  • Prefer React.memo: the primary optimization for function components
  • Use useMemo/useCallback judiciously: don’t overuse them, or you’ll increase memory overhead
  • Avoid inline functions and objects: define them outside the component or cache them with Hooks

Tip: before optimizing, use React DevTools Profiler to confirm the performance bottleneck and avoid premature optimization.