React.memo有什么作用以及如何使用

前言

React.memo() 是一个高阶组件(HOC),用于优化函数组件的性能。它通过记忆组件渲染结果的方式,在组件接收相同的 props 时跳过渲染操作。

1. 基本使用方式

import React, { useState } from 'react';

// 子组件
const ExpensiveComponent = React.memo(({ title, count }) => {
  console.log('ExpensiveComponent rendered!');
  return (
    <div>
      <h2>{title}</h2>
      <p>Count: {count}</p>
    </div>
  );
});

// 父组件
function ParentComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  return (
    <div>
      <input 
        value={text} 
        onChange={(e) => setText(e.target.value)} 
        placeholder="Type something..."
      />
      <button onClick={() => setCount(c => c + 1)}>
        Increment Count
      </button>
      
      <ExpensiveComponent 
        title="Memo Demo" 
        count={count} 
      />
    </div>
  );
}

在这个例子中:

  • ExpensiveComponentReact.memo 包裹
  • 只有当 titlecount 改变时,ExpensiveComponent 才会重新渲染
    当父组件中的 text 状态改变时,ExpensiveComponent 不会重新渲染

2. 使用自定义比较函数

import React, { useState } from 'react';

// 自定义比较函数
const areEqual = (prevProps, nextProps) => {
  // 只有当数组长度发生变化时才重新渲染
  return prevProps.items.length === nextProps.items.length;
};

const ItemsList = React.memo(({ items }) => {
  console.log('ItemsList rendered!');
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}, areEqual);

function ListContainer() {
  const [items, setItems] = useState(['Item 1', 'Item 2']);
  const [counter, setCounter] = useState(0);

  const updateLastItem = () => {
    setItems(prev => {
      const newItems = [...prev];
      newItems[newItems.length - 1] += ' updated';
      return newItems;
    });
  };

  return (
    <div>
      <button onClick={() => setCounter(c => c + 1)}>
        Increment Counter: {counter}
      </button>
      <button onClick={updateLastItem}>Update Last Item</button>
      <ItemsList items={items} />
    </div>
  );
}

这个例子展示了:

  • 如何使用自定义比较函数
  • 只关注特定的属性变化
  • 可以实现更精细的渲染控制

3. 处理对象和函数 props

import React, { useState, useCallback, useMemo } from 'react';

const UserProfile = React.memo(({ user, onUpdate }) => {
  console.log('UserProfile rendered!');
  return (
    <div>
      <h3>{user.name}</h3>
      <p>Age: {user.age}</p>
      <button onClick={onUpdate}>Update Age</button>
    </div>
  );
});

function UserContainer() {
  const [user, setUser] = useState({ name: 'John', age: 25 });
  const [theme, setTheme] = useState('light');

  // 使用 useCallback 记忆函数
  const handleUpdate = useCallback(() => {
    setUser(prev => ({
      ...prev,
      age: prev.age + 1
    }));
  }, []); // 空依赖数组,函数永远不会改变

  // 使用 useMemo 记忆对象
  const memoizedUser = useMemo(() => user, [user.name, user.age]);

  return (
    <div>
      <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
        Toggle Theme: {theme}
      </button>
      
      <UserProfile 
        user={memoizedUser}
        onUpdate={handleUpdate}
      />
    </div>
  );
}

这个例子说明:

  • 如何正确处理对象类型的 props
  • 使用 useCallback 处理函数 props
  • 使用 useMemo 处理对象 props
  • 避免不必要的重新渲染

4. 条件性能优化

import React, { useState } from 'react';

// 创建一个计数器来跟踪渲染次数
const withRenderCount = (WrappedComponent) => {
  return function WithRenderCount(props) {
    const [renderCount, setRenderCount] = useState(0);
    
    React.useEffect(() => {
      setRenderCount(prev => prev + 1);
    });

    return (
      <div>
        <div>Render count: {renderCount}</div>
        <WrappedComponent {...props} />
      </div>
    );
  };
};

// 创建两个组件:一个使用 memo,一个不使用
const RegularComponent = withRenderCount(({ value }) => {
  return <div>Regular: {value}</div>;
});

const MemoizedComponent = withRenderCount(React.memo(({ value }) => {
  return <div>Memoized: {value}</div>;
}));

function ComparisonContainer() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  return (
    <div>
      <div>
        <input 
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder="Type to trigger parent update"
        />
        <button onClick={() => setCount(c => c + 1)}>
          Increment Count
        </button>
      </div>

      <RegularComponent value={count} />
      <MemoizedComponent value={count} />
    </div>
  );
}

这个例子展示了:

  • 使用和不使用 memo 的性能差异
  • 通过渲染计数直观展示优化效果
  • 何时使用 memo 是有意义的

5. 注意事项和最佳实践

import React, { useState, useCallback } from 'react';

// ❌ 不恰当的使用方式
const BadExample = React.memo(({ items, onItemClick }) => {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index} onClick={() => onItemClick(item)}>
          {item}
        </li>
      ))}
    </ul>
  );
});

// ✅ 正确的使用方式
const GoodExample = React.memo(({ items, onItemClick }) => {
  return (
    <ul>
      {items.map((item, index) => (
        <ListItem 
          key={item.id} 
          item={item} 
          onClick={onItemClick} 
        />
      ))}
    </ul>
  );
});

// 分离的列表项组件
const ListItem = React.memo(({ item, onClick }) => {
  return (
    <li onClick={() => onClick(item)}>
      {item.text}
    </li>
  );
});

function Container() {
  const [items, setItems] = useState([
    { id: 1, text: 'Item 1' },
    { id: 2, text: 'Item 2' }
  ]);

  const handleItemClick = useCallback((item) => {
    console.log('Clicked:', item);
  }, []);

  return (
    <div>
      <GoodExample 
        items={items}
        onItemClick={handleItemClick}
      />
    </div>
  );
}

最佳实践总结:

  1. 何时使用 React.memo
  • 组件经常重新渲染
  • 组件接收相同 props 时渲染输出不变
  • 组件包含复杂的计算逻辑
  1. 何时不使用 React.memo
  • 组件很简单,渲染成本低
  • props 经常变化
  • 组件需要频繁更新

3. 优化建议:

  • 配合 useCallbackuseMemo 使用
  • 合理设计组件的 props 结构
  • 避免传递内联对象和函数
  • 使用不可变数据结构
  • 适当拆分组件

React.memo 是一个强大的性能优化工具,但需要谨慎使用。过度优化可能会适得其反,增加代码复杂度而没有明显的性能提升。建议在实际遇到性能问题时再考虑使用 React.memo

关于我
loading
下一篇: