跳至主要內容

useCallback在多次渲染中缓存函数

Mr.He大约 2 分钟

useCallback在多次渲染中缓存函数

跳过组件的重新渲染

默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件,因此每当因 theme 更改时而 ProductPage 组件重新渲染时,ShippingForm 组件也会重新渲染。

import { useCallback } from 'react';

function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);

  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

你可以将 ShippingForm 组件包裹在 memo 中。

如果 props 和上一次渲染时相同,那么 ShippingForm 组件将跳过重新渲染。

import { memo } from 'react';

const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  // ...
});

useCallback中更新state,减少依赖

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

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos([...todos, newTodo]);
  }, [todos]);
  // ...
}

期望记忆化函数具有尽可能少的依赖,当你读取 state 只是为了计算下一个 state 时,你可以通过传递 updater function 以移除该依赖

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

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos(todos => [...todos, newTodo]);
  }, []); // ✅ 不需要 todos 依赖项
  // ...
}

Effect内部调用函数,频繁触发执行

有时,你想要在 Effect 内部调用函数

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  function createOptions() {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    // ...
  }, [createOptions]) //🔴 问题:这个依赖在每一次渲染中都会发生改变
}

**每一个响应值都必须声明为 Effect 的依赖。**但是如果将 createOptions 声明为依赖,它会导致 Effect 不断执行

解决办法:

在 Effect 中将要调用的函数包裹在 useCallback

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }, [roomId]); // ✅ 仅当 roomId 更改时更改

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // ✅ 仅当 createOptions 更改时更改
}

这将确保如果 roomId 相同,createOptions 在多次渲染中会是同一个函数

最好消除对函数依赖项的需求。将你的函数移入 Effect 内部

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() { // ✅ 无需使用回调或函数依赖!
      return {
        serverUrl: 'https://localhost:1234',
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // ✅ 仅当 roomId 更改时更改
  // ...
}

优化自定义 Hook

编写一个 自定义 Hook,建议将它返回的任何函数包裹在 useCallback 中, 这确保了 Hook 的使用者在需要时能够优化自己的代码

function useRouter() {
  const { dispatch } = useContext(RouterStateContext);

  const navigate = useCallback((url) => {
    dispatch({ type: 'navigate', url });
  }, [dispatch]);

  const goBack = useCallback(() => {
    dispatch({ type: 'back' });
  }, [dispatch]);

  return {
    navigate,
    goBack,
  };
}