React 构建组件的方式有哪些?区别?

一、是什么

在 React 中,组件是构建用户界面的基本单元。随着 React 版本的迭代,构建组件的方式也在不断演进。从最初的 createClass,到 ES6 类组件,再到如今主流的函数组件 + Hooks,React 提供了多种构建组件的方式来满足不同的场景需求。

理解各种组件构建方式及其特点,有助于在实际项目中选择最合适的方案。

二、函数组件

函数组件是目前 React 官方推荐的主要组件形式。它本质上就是一个接收 props 并返回 JSX 的纯函数。

interface UserCardProps {
  name: string;
  age: number;
}

function UserCard({ name, age }: UserCardProps) {
  return (
    <div className="user-card">
      <h2>{name}</h2>
      <p>年龄:{age}</p>
    </div>
  );
}

在 React 16.8 引入 Hooks 之后,函数组件获得了管理状态和副作用的能力:

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    document.title = `点击了 ${count} 次`;
  }, [count]);

  return (
    <div>
      <p>当前计数:{count}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  );
}

函数组件的优势:

  • 代码更简洁,没有 this 绑定问题
  • 逻辑复用更方便(自定义 Hooks)
  • 更容易进行测试
  • 未来 React 优化(如 React Compiler)主要针对函数组件

三、类组件

类组件通过继承 React.ComponentReact.PureComponent 来创建,拥有完整的生命周期方法和内部状态管理能力。

import React, { Component } from 'react';

interface CounterState {
  count: number;
}

class Counter extends Component<{}, CounterState> {
  state: CounterState = { count: 0 };

  handleClick = () => {
    this.setState(prev => ({ count: prev.count + 1 }));
  };

  componentDidMount() {
    document.title = `点击了 ${this.state.count} 次`;
  }

  componentDidUpdate() {
    document.title = `点击了 ${this.state.count} 次`;
  }

  render() {
    return (
      <div>
        <p>当前计数:{this.state.count}</p>
        <button onClick={this.handleClick}>+1</button>
      </div>
    );
  }
}

类组件目前仍然可以正常使用,但 React 团队已不再推荐新代码使用类组件。不过 Error Boundary 仍然需要类组件来实现(截至 React 19)。

class ErrorBoundary extends Component<
  { children: React.ReactNode; fallback: React.ReactNode },
  { hasError: boolean }
> {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error, info: React.ErrorInfo) {
    console.error('组件错误:', error, info);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback;
    }
    return this.props.children;
  }
}

四、React.memo 包裹组件

React.memo 是一个高阶组件,用于对函数组件进行浅比较的性能优化,当 props 未发生变化时跳过重新渲染。

import { memo } from 'react';

interface ExpensiveListProps {
  items: string[];
  onItemClick: (item: string) => void;
}

const ExpensiveList = memo(function ExpensiveList({
  items,
  onItemClick,
}: ExpensiveListProps) {
  console.log('ExpensiveList 渲染');
  return (
    <ul>
      {items.map(item => (
        <li key={item} onClick={() => onItemClick(item)}>
          {item}
        </li>
      ))}
    </ul>
  );
});

也可以传入自定义比较函数:

const MemoizedComponent = memo(MyComponent, (prevProps, nextProps) => {
  return prevProps.id === nextProps.id;
});

在 React 19 中,React Compiler 可以自动对组件进行类似的记忆化优化,减少手动使用 memo 的需求。

五、forwardRef 组件

React.forwardRef 用于将父组件传递的 ref 转发到子组件内部的 DOM 元素或组件实例上。

import { forwardRef, useRef } from 'react';

interface InputProps {
  label: string;
  placeholder?: string;
}

const LabeledInput = forwardRef<HTMLInputElement, InputProps>(
  function LabeledInput({ label, placeholder }, ref) {
    return (
      <div className="form-field">
        <label>{label}</label>
        <input ref={ref} placeholder={placeholder} />
      </div>
    );
  }
);

function Form() {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleFocus = () => {
    inputRef.current?.focus();
  };

  return (
    <div>
      <LabeledInput ref={inputRef} label="用户名" placeholder="请输入用户名" />
      <button onClick={handleFocus}>聚焦输入框</button>
    </div>
  );
}

在 React 19 中,ref 可以作为普通 prop 传递,不再强制需要 forwardRef 包装:

function LabeledInput({ label, placeholder, ref }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
  return (
    <div className="form-field">
      <label>{label}</label>
      <input ref={ref} placeholder={placeholder} />
    </div>
  );
}

六、lazy 懒加载组件

React.lazy 配合动态 import() 实现组件的按需加载,常与 Suspense 搭配使用来展示加载状态。

import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

lazy 组件在首次渲染时才会加载对应的代码块,有效减少了应用的初始包体积。

七、PureComponent

PureComponent 是类组件的一个变体,它自动实现了 shouldComponentUpdate,对 props 和 state 进行浅比较来决定是否重新渲染。

import React, { PureComponent } from 'react';

class PureCounter extends PureComponent<{ count: number }> {
  render() {
    console.log('PureCounter 渲染');
    return <span>{this.props.count}</span>;
  }
}

它等价于函数组件中的 React.memo,但仅适用于类组件场景。

八、各种方式对比

特性函数组件类组件memo 组件forwardRef 组件lazy 组件
语法复杂度
状态管理Hooksthis.stateHooksHooks同被包裹组件
性能优化useMemo/useCallbackPureComponent/SCU自动浅比较无额外优化代码分割
ref 支持React 19 原生支持直接支持支持核心功能支持
错误边界不支持支持不支持不支持不支持
适用场景绝大多数场景Error Boundary性能敏感组件组件库封装路由级组件

九、选择建议

在现代 React 开发中,推荐遵循以下原则:

  1. 默认使用函数组件 + Hooks:绝大多数业务组件都应该使用函数组件
  2. 需要性能优化时使用 memo:当子组件渲染开销大且 props 变化频率低时
  3. 组件库开发使用 forwardRef:需要暴露 DOM 引用给消费者时
  4. 路由级组件使用 lazy:通过代码分割减少首屏加载时间
  5. Error Boundary 使用类组件:这是目前唯一必须使用类组件的场景
// 现代 React 项目中的典型组件组织
import { lazy, Suspense, memo, forwardRef } from 'react';

const LazyPage = lazy(() => import('./HeavyPage'));

const OptimizedList = memo(function OptimizedList({ data }: { data: Item[] }) {
  return <ul>{data.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
});

const CustomInput = forwardRef<HTMLInputElement, InputProps>(
  function CustomInput(props, ref) {
    return <input ref={ref} {...props} />;
  }
);

function App() {
  return (
    <ErrorBoundary fallback={<p>出错了</p>}>
      <Suspense fallback={<p>加载中...</p>}>
        <LazyPage />
      </Suspense>
    </ErrorBoundary>
  );
}

十、总结

React 提供了多种构建组件的方式,每种方式都有其特定的使用场景。函数组件 + Hooks 是当前及未来的主流方向,React Compiler(React 19+)的引入将进一步简化性能优化工作。理解每种组件形式的特点和适用场景,能够帮助开发者做出更合理的技术选型决策。