Coding Hao

详解React Hook 之useCallback

2024年6月17日 (1年前)

介绍

在 React 中,每次组件重新渲染时,函数都会被重新创建。如果这些函数被传递给子组件,可能会导致子组件不必要的重新渲染(即使子组件的 props 没有变化)。useCallback 可以缓存函数,确保在依赖项不变的情况下,函数引用保持不变。

基本用法

useCallback 接受两个参数:

  • 函数:需要缓存的函数。
  • 依赖项数组:当依赖项发生变化时,函数会重新创建。
const memoizedCallback = useCallback(
  () => {
    // 函数逻辑
  },
  [依赖项], // 依赖项数组
);

使用场景

场景 1:避免子组件不必要的渲染: 当父组件传递一个函数给子组件时,使用 useCallback 可以避免子组件因父组件重新渲染而重新渲染。

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

function Child({ onClick }) {
  console.log('Child rendered');
  return <button onClick={onClick}>Click me</button>;
}

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

  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []); // 空依赖项数组,函数不会重新创建

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={()=> setCount(count + 1)}>Increment</button>
      <Child onClick={handleClick} />
    </div>
  );
}

场景 2:依赖项变化时重新创建函数, 当函数依赖某些状态或 props 时,可以将这些依赖项添加到依赖项数组中。

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

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

  const handleClick = useCallback(() => {
    console.log('Count:', count);
  }, [count]); // 当 count 变化时,函数会重新创建

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={()=> setCount(count + 1)}>Increment</button>
      <button onClick={handleClick}>Log Count</button>
    </div>
  );
}

注意事项

  • 依赖项数组:
    • 如果依赖项数组为空([]),函数只会在组件挂载时创建一次。
    • 如果依赖项数组包含变量,当这些变量变化时,函数会重新创建。
  • 性能优化:
    • 只有在确实需要优化性能时使用 useCallback。过度使用可能会导致代码复杂化。
  • 与 useMemo 的区别:
    • useCallback 缓存的是函数,而 useMemo 缓存的是函数的返回值。

示例:结合 useMemo 使用

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

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

  const handleClick = useCallback(() => {
    console.log('Count:', count);
  }, [count]);

  const memoizedValue = useMemo(() => {
    return count * 2; // 缓存计算结果
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Memoized Value: {memoizedValue}</p>
      <button onClick={()=> setCount(count + 1)}>Increment</button>
      <button onClick={handleClick}>Log Count</button>
    </div>
  );
}

注意事项

  • 浅比较的限制:
    • React.memo 默认使用浅比较,如果 props 是对象或数组,浅比较可能无法检测到内部变化。
    • 对于复杂 props,建议使用自定义比较函数。
  • 不要滥用 React.memo
    • 对于简单组件,React.memo 可能不会带来明显的性能提升,反而会增加比较的开销, 因为比较 props 本身也需要计算开销,如果组件经常接收不同的 props,使用 memo 反而会增加额外开销
    • 只有在组件渲染开销较大或 props 变化较少时,才建议使用 React.memo。
  • useMemouseCallback 结合使用:
    • 如果 props 包含函数或复杂对象,可以使用 useCallbackuseMemo 来避免不必要的重新创建。
import React, { useState, memo, useCallback } from 'react';

const Child = memo(function Child({ onClick }) {
  console.log('Child rendered');
  return <button onClick={onClick}>Click Me</button>;
});

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

  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

  return (
    <div>
      <button onClick={()=> setCount(count + 1)}>Increment</button>
      <p>Count: {count}</p>
      <Child onClick={handleClick} />
    </div>
  );
}

export default Parent;

总结

  • useCallback 用于缓存函数,避免在组件重新渲染时创建新的函数实例。
  • 适用场景:优化函数传递、避免子组件不必要的渲染。
  • 依赖项数组:控制函数何时重新创建。
  • useMemo 的区别:useCallback 缓存函数,useMemo 缓存函数的返回值。