useCallback

102 阅读1分钟

useMemo有点像,但是主要用于在 React 组件中缓存函数,特别是当这些函数被当作 props 传递给子组件,或者用于事件处理和触发重新渲染时。这样可以避免在组件的每次重新渲染时创建新的函数实例,从而优化性能。

基本使用

import React, { useCallback } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = ({ itemId }) => {
  const handleClick = useCallback(() => {
    console.log(`Clicked on item ${itemId}`);
  }, [itemId]);

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

与 useEffect 搭配

还可以与useEffect一起使用

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

const MyComponent = ({ userId }) => {
  const [query, setQuery] = useState('');

  const fetchData = useCallback(async () => {
    const url = `https://api.example.com/data?user=${userId}&query=${query}`;
    const response = await fetch(url);
    const data = await response.json();
    console.log('Data fetched:', data);
  }, [userId, query]);  // `fetchData` 现在依赖于 userId 和 query

  useEffect(() => {
    fetchData();
  }, [fetchData]); // 仅在 `fetchData` 发生变化时调用一次

  return (
    <div>
      <input type="text" value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={fetchData}>Search</button>
    </div>
  );
};

以上代码就是当搜索输入框进行输入操作时,调用setQueryuseCallback的依赖发生变化时,会进行函数的再次执行。

那么这里引申出一个问题

问题

useEffect的依赖中放fetchData(经过useCallback处理的函数),有什么用,直接放query不行吗?

回答一 :

fetchData 是通过 useCallback 创建的一个被缓存的函数。尽管 fetchData 是一个函数,但它的引用可以在不同的渲染之间变化。当 useCallback 的依赖数组中的项发生变化时,它会返回一个新的函数实例。在例子中,fetchData 依赖于 query,这意味着每当 query 改变时,fetchData 就会变成一个新的引用。这就是为什么说 fetchData 可以变化的原因。

回答二 : 以上实例代码不是完整的业务代码,如果仅仅是 query 变化就重新执行搜索,那不搭配使用也可以,但是useCallback的本质是执行一个函数,在这个搜索框的 onChange 方法被触发时,不能立即请求,等用户输入完再进行请求,所以在这里要使用一个防抖函数,输入后等待一段时间才判定输入完成,这样可以减少网络的请求次数。

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

// 简单的防抖函数实现
const debounce = (func, delay) => {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
};

const MyComponent = ({ userId }) => {
  const [query, setQuery] = useState('');

  const fetchData = useCallback(async () => {
    const url = `https://api.example.com/data?user=${userId}&query=${query}`;
    const response = await fetch(url);
    const data = await response.json();
    console.log('Data fetched:', data);
  }, [userId, query]);  // `fetchData` 现在依赖于 userId 和 query

  // 使用防抖函数包装 handleChange
  const handleChange = debounce((event) => {
    setQuery(event.target.value);
  }, 300); // 延迟 300ms

  return (
    <div>
      <input type="text" value={query} onChange={handleChange} />
    </div>
  );
};

定义了一个名为 debounce 的函数,它接受一个函数和一个延迟时间作为参数,返回一个新的函数。这个新的函数在被调用时会等待一段时间,如果在这段时间内再次被调用,旧的定时器会被清除,然后重新设置一个新的定时器,以确保在延迟时间结束后才会执行函数。