当前位置: 首页 > article >正文

React 中hooks之 React useCallback使用方法总结

1. useCallback 基础概念

useCallback 是 React 的一个 Hook,用于记忆函数定义,避免在每次渲染时创建新的函数实例。它在需要将回调函数传递给经过优化的子组件时特别有用。
当state变化的时候引起组件重新渲染执行会导致某个方法被反复创建增加内存负担,这个时候可以使用useCallback将该函数进行缓存,只创建一次

1.1 基本语法

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b], // 依赖项数组
);

同样的当依赖项省略时组件重新渲染都会执行,当依赖项为空数组的时候只有组件初始化的时候会执行一次,数组里有依赖项的时候依赖项发生变化的时候都会缓存一次

1.2 与普通函数的区别

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

  // ❌ 每次渲染都会创建新的函数实例
  const handleClick = () => {
    console.log('Clicked');
  };

  // ✅ 函数实例会被记忆,只在依赖项变化时更新
  const handleClickMemoized = useCallback(() => {
    console.log('Clicked');
  }, []); // 空依赖数组,函数永远不会改变

  return <ChildComponent onClick={handleClickMemoized} />;
}

2. useCallback 配合 React.memo 使用

2.1 基本示例

// 子组件使用 React.memo 优化
const ChildComponent = React.memo(function ChildComponent({ onClick }) {
  console.log("ChildComponent rendered");
  return <button onClick={onClick}>Click me</button>;
});

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

  // 使用 useCallback 记忆回调函数
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []); // 空依赖数组,因为不依赖任何值

  return (
    <div>
      <input value={text} onChange={e => setText(e.target.value)} />
      <p>Count: {count}</p>
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

2.2 带有依赖项的示例

function SearchComponent({ onSearch }) {
  const [searchTerm, setSearchTerm] = useState("");
  const [searchHistory, setSearchHistory] = useState([]);

  // 使用 useCallback 记忆搜索函数
  const handleSearch = useCallback(() => {
    if (searchTerm.trim()) {
      onSearch(searchTerm);
      setSearchHistory(prev => [...prev, searchTerm]);
    }
  }, [searchTerm, onSearch]); // 依赖 searchTerm 和 onSearch

  return (
    <div>
      <input
        value={searchTerm}
        onChange={e => setSearchTerm(e.target.value)}
      />
      <SearchButton onClick={handleSearch} />
      <SearchHistory items={searchHistory} />
    </div>
  );
}

// 优化的子组件
const SearchButton = React.memo(function SearchButton({ onClick }) {
  console.log("SearchButton rendered");
  return <button onClick={onClick}>搜索</button>;
});

const SearchHistory = React.memo(function SearchHistory({ items }) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
});

3. 实际应用场景

3.1 表单处理

function ComplexForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: ''
  });

  // 记忆表单字段更新函数
  const handleFieldChange = useCallback((fieldName) => (event) => {
    setFormData(prev => ({
      ...prev,
      [fieldName]: event.target.value
    }));
  }, []); // 不需要依赖项,因为使用了函数式更新

  return (
    <form>
      <FormField
        label="Name"
        value={formData.name}
        onChange={handleFieldChange('name')}
      />
      <FormField
        label="Email"
        value={formData.email}
        onChange={handleFieldChange('email')}
      />
      <FormField
        label="Message"
        value={formData.message}
        onChange={handleFieldChange('message')}
      />
    </form>
  );
}

const FormField = React.memo(function FormField({ label, value, onChange }) {
  console.log(`${label} field rendered`);
  return (
    <div>
      <label>{label}</label>
      <input value={value} onChange={onChange} />
    </div>
  );
});

3.2 列表渲染优化

function TodoList() {
  const [todos, setTodos] = useState([]);

  // 记忆添加任务函数
  const handleAdd = useCallback((text) => {
    setTodos(prev => [...prev, { id: Date.now(), text, completed: false }]);
  }, []);

  // 记忆切换完成状态函数
  const handleToggle = useCallback((id) => {
    setTodos(prev =>
      prev.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  }, []);

  // 记忆删除函数
  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, []);

  return (
    <div>
      <AddTodo onAdd={handleAdd} />
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={handleToggle}
          onDelete={handleDelete}
        />
      ))}
    </div>
  );
}

const TodoItem = React.memo(function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <div>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
      <button onClick={() => onDelete(todo.id)}>删除</button>
    </div>
  );
});

4. 性能优化最佳实践

4.1 合理使用依赖项

function UserProfile({ userId, onUpdate }) {
  // ✅ 只在 userId 或 onUpdate 变化时更新
  const handleUpdate = useCallback(() => {
    onUpdate(userId);
  }, [userId, onUpdate]);

  // ❌ 不必要的依赖项
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, [userId]); // userId 不需要作为依赖项
}

4.2 避免过度优化

// ❌ 简单组件不需要使用 useCallback
function SimpleButton({ onClick }) {
  return <button onClick={onClick}>Click me</button>;
}

// ✅ 复杂组件或频繁重渲染的组件使用 useCallback
const ComplexComponent = React.memo(function ComplexComponent({ onAction }) {
  // 复杂的渲染逻辑
  return (
    // ...
  );
});

5. useCallback 与其他 Hooks 配合

5.1 配合 useEffect 使用

function DataFetcher({ query }) {
  const [data, setData] = useState(null);

  // 记忆获取数据的函数
  const fetchData = useCallback(async () => {
    const response = await fetch(`/api/search?q=${query}`);
    const result = await response.json();
    setData(result);
  }, [query]);

  // 在 effect 中使用记忆的函数
  useEffect(() => {
    fetchData();
  }, [fetchData]); // fetchData 作为依赖项

  return <div>{/* 渲染数据 */}</div>;
}

5.2 配合 useMemo 使用

function DataProcessor({ data, onProcess }) {
  // 记忆处理函数
  const processData = useCallback((item) => {
    // 复杂的数据处理逻辑
    return someExpensiveOperation(item);
  }, []);

  // 使用记忆的函数处理数据
  const processedData = useMemo(() => {
    return data.map(processData);
  }, [data, processData]);

  return (
    <div>
      {processedData.map(item => (
        <ProcessedItem
          key={item.id}
          item={item}
          onProcess={onProcess}
        />
      ))}
    </div>
  );
}

6. 注意事项

  1. 避免过度使用

    • 只在性能确实受影响时使用
    • 简单组件和回调不需要使用 useCallback
  2. 正确设置依赖项

    • 包含所有回调中使用的变量
    • 避免不必要的依赖项
  3. 配合 React.memo 使用

    • 单独使用 useCallback 可能无法带来性能提升
    • 需要配合 React.memo 等优化手段
  4. 考虑使用场景

    • 频繁重渲染的组件
    • 复杂的计算或操作
    • 传递给多个子组件的回调

通过合理使用 useCallback 和 React.memo,我们可以有效优化 React 应用的性能。但要记住,过度优化可能会适得其反,应该在实际需要时才进行优化。


http://www.kler.cn/a/511867.html

相关文章:

  • 基于微信小程序的安心陪诊管理系统
  • IoTDB 常见问题 QA 第四期
  • Django简介与虚拟环境安装Django
  • DETR论文阅读
  • OA-CNN:用于 3D 语义分割的全自适应稀疏 CNN
  • Python爬取豆瓣图书网Top250 实战
  • Java 基于微信小程序的原创音乐小程序设计与实现(附源码,部署,文档)
  • Centos7搭建PHP项目,环境(Apache+PHP7.4+Mysql5.7)
  • ubuntu系统文件查找、关键字搜索
  • 2024:成长、创作与平衡的年度全景回顾
  • RabbitMQ---事务及消息分发
  • 【Redis】5种基础数据结构介绍及应用
  • 【MCU】CH591用软件 I2C 出现的 bug
  • 我的创作纪念日——我与CSDN一起走过的365天
  • 从Windows通过XRDP远程访问和控制银河麒麟ukey v10服务器,以及多次连接后黑屏的问题
  • 无数据库开源Wiki引擎WikiDocs
  • Spring的Bean:Bean的生命周期(包括实践)
  • CSS实现实现票据效果 mask与切图方式
  • uniapp——App 监听下载文件状态,打开文件(三)
  • RabbitMQ---应用问题
  • 回顾2024年度 - 挑战之旅:学习、生活与成长的华丽蜕变
  • 【无标题】微调是迁移学习吗?
  • Django简介与虚拟环境安装Django
  • leetcode763.划分字母区间
  • Android 存储进化:分区存储
  • 【博客之星2024年度总评选】年度回望:我的博客之路与星光熠熠