函数组件的声明方式及差异+React.memo和userCallback区别

1、函数组件的声明方式及差异

  • 普通函数声明

  • 箭头函数声明

  • 使用React.FC类型(TypeScript专用)

    react 复制代码
    interface Props {
      content: string
    }
    
    // 写法一
    const MyComponent: React.FC<Props> = (props) => {
      return <div>{props.content}</div>
    }
    
    // 写法二, FC 是 React.FC 的简写别名,二者完全等价。
    import { FC } from 'react'
    const MyComponent: FC<Props> = (props) => {
      return <div>{props.content}</div>
    }
  • 使用React.memo优化

    react 复制代码
    const MemoizedComponent = React.memo(function MyComponent(props) {
      return <div>{props.content}</div>;
    });

2、React.memo和userCallback

React.memo

  1. 介绍及使用时机

    React.memo 是什么:一个用于函数组件的性能优化工具,通过浅比较 props 避免无效渲染。

    何时使用

    • 组件渲染成本高
    • 父组件频繁更新但子组件 props 不变
    • 需要精细化控制渲染逻辑

    如何正确使用

    • 对简单 props 使用默认浅比较
    • 对复杂 props 结合 useMemo/useCallback
    • 必要时自定义比较函数
  2. 基本用法

    react 复制代码
    import React from 'react';
    
    const MyComponent = (props) => {
      return <div>{props.content}</div>;
    };
    
    // 用 React.memo 包裹组件
    export default React.memo(MyComponent);
  3. 自定义比较逻辑(高级用法)

    如果默认的浅比较不满足需求,可以手动控制比较逻辑:

    react 复制代码
    // 第二个参数是自定义比较函数
    export default React.memo(MyComponent, (prevProps, nextProps) => {
      // 返回 true 表示 props "相同",阻止渲染
      // 返回 false 表示 props "不同",允许渲染
      return prevProps.id === nextProps.id;
    });
  4. 使用场景案例

    react 复制代码
    场景 1:父组件频繁更新,但子组件不需要
    
    // 父组件
    const Parent = () => {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <button onClick={() => setCount(count + 1)}>点击 {count}</button>
          {/* 即使 count 变化,Child 的 props 未变化 */}
          <Child title="静态内容" />
        </div>
      );
    };
    
    // 子组件用 React.memo 包裹
    const Child = React.memo(({ title }) => {
      console.log('子组件渲染'); // 只有 title 变化时才会打印
      return <div>{title}</div>;
    });
    
    场景 2:复杂数据渲染
    // 大数据表格组件
    const DataTable = React.memo(({ data }) => {
      // 假设 data 有 10,000 行数据
      return (
        <table>
          {data.map(row => (
            <tr key={row.id}><td>{row.value}</td></tr>
          ))}
        </table>
      );
    });

useCallback

  1. 核心作用

    useCallback 是 React 的一个性能优化 Hook,用于 缓存函数引用

    它的核心价值是 避免父组件重新渲染时,因函数引用变化导致子组件无效渲染

解决什么问题?

  • 问题场景
    当父组件传递一个函数给子组件时,若父组件更新,每次会重新创建该函数。即使子组件用 React.memo 包裹,也会因函数引用不同而触发重新渲染。
  • 优化目标
    保持函数引用不变,除非依赖项变化,从而减少子组件无效渲染。

问题场景:

react 复制代码
// 父组件
const Parent = () => {
  const [count, setCount] = useState(0);

  // 每次 Parent 渲染都会创建新的 handleClick 函数
  const handleClick = () => {
    console.log('点击事件');
  };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>触发渲染(当前:{count})</button>
      {/* 即使 Child 用 React.memo 包裹,仍会重新渲染 */}
      <Child onClick={handleClick} />
    </div>
  );
};

// 子组件(用 React.memo 优化)
const Child = React.memo(({ onClick }) => {
  console.log('子组件渲染'); // 父组件每次更新都会触发此日志
  return <button onClick={onClick}>子组件按钮</button>;
});

// 问题:父组件每次更新(如 count 变化)时,handleClick 会被重新创建,导致传递给子组件的 onClick 引用不同。
// 结果:即使子组件用 React.memo 包裹,也会因 props 中函数的引用变化而重新渲染。

语法与参数

react 复制代码
const memoizedFn = useCallback(
  () => { /* 函数逻辑 */ }, 
  [dep1, dep2] // 依赖项数组
);
// 参数 1:需要缓存的函数。
// 参数 2:依赖项数组(类似 useEffect 的依赖)。
// 返回值:一个记忆化的函数引用(依赖不变时引用不变)。

// ✅ 正确:依赖项包含 count,避免形成闭包并解决
const handleClick = useCallback(() => {
  console.log(count);
}, [count]);

区别

useCallback useMemo
缓存目标 缓存函数本身 缓存函数的计算结果(值/对象)
返回值 函数引用 计算结果
等效写法 useMemo(() => fn, deps) useMemo(() => value, deps)

2025/04/15