你不知道的React系列(十九)useCallback

123 阅读1分钟

本文正在参加「金石计划」

const cachedFn = useCallback(fn, dependencies)
  • 在重新渲染之间缓存函数定义

  • fn 可以接收任意参数,返回任意数据

  • 初次渲染返回 fn, dependencies 没有改变返回相同fn, dependencies改变返回不同的fn

子组件跳过重新渲染

import { memo } from 'react';

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

function ProductPage({ productId, referrer, theme }) {
  // Tell React to cache your function between re-renders...
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]); // ...so as long as these dependencies don't change...

  return (
    <div className={theme}>
      {/* ...ShippingForm will receive the same props and can skip re-rendering */}
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

缓存函数中更新 state(不建议)

使用 updater functions

Effect 触发太频繁(不建议)

把函数放在 Effect 里面

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

  const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }, [roomId]); // ✅ Only changes when roomId changes

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // ✅ Only changes when createOptions changes
  // ...

自定义 hook 使用 useCallback

把所有导出的函数使用 useCallback 包裹

useCallback 和 useMemo

useMemo 缓存调用函数的结果。

useCallback 缓存函数本身。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

除了使用 useMemo,一些其他建议

  • 组件作为 children 传递时
  • 减少 state 使用和提升
  • 渲染逻辑保持纯净
  • 避免在 Effect 中更新 state
  • 尽量减少 Effect dependencies

问答

  • 组件每次渲染, useCallback 返回一个不同的函数

    没有指定 dependencies 或者 dependencies 依赖项每次返回的数据都不一样

  • 循环中调用 useCallback

    提取循环内容为一个组件,在组件内部使用 useCallback, 可以缓存某个数据或者整个组件

    function ReportList({ items }) {
      return (
        <article>
          {items.map(item => {
            // 🔴 You can't call useCallback in a loop like this:
            const handleClick = useCallback(() => {
              sendReport(item)
            }, [item]);
    
            return (
              <figure key={item.id}>
                <Chart onClick={handleClick} />
              </figure>
            );
          })}
        </article>
      );
    }
    
    function ReportList({ items }) {
      return (
        <article>
          {items.map(item =>
            <Report key={item.id} item={item} />
          )}
        </article>
      );
    }
    
    function Report({ item }) {
      // ✅ Call useCallback at the top level:
      const handleClick = useCallback(() => {
        sendReport(item)
      }, [item]);
    
      return (
        <figure>
          <Chart onClick={handleClick} />
        </figure>
      );
    }
    
    function ReportList({ items }) {
      // ...
    }
    
    const Report = memo(function Report({ item }) {
      function handleClick() {
        sendReport(item);
      }
    
      return (
        <figure>
          <Chart onClick={handleClick} />
        </figure>
      );
    });