React 的 类组件 和 函数式组件 有什么区别?

31 阅读5分钟

React 的函数式组件和类组件是两种不同的组件编写方式,它们在功能上逐渐趋近,但仍有明显的区别和适用场景。以下是它们的联系与区别分析:


一、联系

  1. 核心目标相同无论是函数式组件还是类组件,都是为了构建 React UI,接收 props,返回 React 元素(JSX)。

  2. 生命周期与副作用

    1. 类组件通过生命周期方法(如 componentDidMountcomponentDidUpdate)处理副作用。

    2. 函数式组件通过 useEffect Hook 模拟生命周期行为。

  3. 状态管理

    1. 类组件通过 this.statethis.setState 管理状态。

    2. 函数式组件通过 useStateuseReducer 等 Hooks 管理状态。

  4. 可复用性

    1. 类组件通过高阶组件(HOC)或 Render Props 复用逻辑。

    2. 函数式组件通过自定义 Hooks(如 useFetch)复用逻辑。


二、区别

1. 写法与语法

  • 函数式组件本质是 JavaScript 函数,直接返回 JSX:

    • function FunctionalComponent(props) {
        return <div>{props.text}</div>;
      }
      
  • 类组件需要继承 React.Component,并通过 render 方法返回 JSX:

    • class ClassComponent extends React.Component {
        render() {
          return <div>{this.props.text}</div>;
        }
      }
      

2. 状态管理方式

  • 函数式组件使用 useState Hook:

    • const [count, setCount] = useState(0);
      
  • 类组件通过 this.statethis.setState

    • class ClassComponent extends React.Component {
        constructor(props) {
          super(props);
          this.state = { count: 0 };
        }
        increment = () => {
          this.setState({ count: this.state.count + 1 });
        };
      }
      

3. 生命周期与副作用处理

  • 函数式组件使用 useEffect 处理副作用,无需拆分不同生命周期:

    • useEffect(() => {
        // 相当于 componentDidMount 和 componentDidUpdate
        console.log("Component mounted or updated");
        return () => {
          // 相当于 componentWillUnmount
          console.log("Component will unmount");
        };
      }, [dependencies]);
      
  • 类组件需要明确定义生命周期方法:

    • componentDidMount() { /* 组件挂载 */ }
      componentDidUpdate() { /* 组件更新 */ }
      componentWillUnmount() { /* 组件卸载 */ }
      

4. this 绑定问题

  • 类组件需要手动绑定 this(如事件处理函数),或使用箭头函数避免:

    • // 需要绑定 this
      <button onClick={this.handleClick.bind(this)}>Click</button>
      // 或使用箭头函数
      handleClick = () => { ... }
      
  • 函数式组件this 绑定问题,直接使用函数内变量。


5. Hooks 的独占性

  • 函数式组件可以使用 Hooks(如 useState, useEffect, useContext 等)。类组件无法使用 Hooks


6. 性能优化

  • 函数式组件通过 React.memo 缓存组件,或使用 useMemouseCallback 优化计算和函数引用。

    • const MemoizedComponent = React.memo(FunctionalComponent);
      
  • 类组件通过 shouldComponentUpdateReact.PureComponent 优化渲染。


7. 未来趋势

  • 函数式组件React 官方推荐使用,尤其是 Hooks 引入后,功能已覆盖类组件的所有场景。新项目优先选择函数式组件

  • 类组件旧项目或特定场景(如错误边界 componentDidCatch)可能仍需要。


三、如何选择?

  1. 新项目优先使用函数式组件 + Hooks,代码更简洁,逻辑更集中。

  2. 旧项目维护类组件可以保留,逐步迁移到函数式组件。

  3. 特定场景需要 componentDidCatch(错误边界)时,仍需使用类组件。

  4. 理解 React 设计哲学:React 推崇组合优于继承,这是 HOC 和 Hooks 的设计核心理念。避免过度继承。


四、示例对比

1. 状态与生命周期

// 函数式组件
function Timer() {
  const [time, setTime] = useState(0);
  useEffect(() => {
    const interval = setInterval(() => {
      setTime(t => t + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);
  return <div>Time: {time}</div>;
}

// 类组件
class Timer extends React.Component {
  state = { time: 0 };
  componentDidMount() {
    this.interval = setInterval(() => {
      this.setState({ time: this.state.time + 1 });
    }, 1000);
  }
  componentWillUnmount() {
    clearInterval(this.interval);
  }
  render() {
    return <div>Time: {this.state.time}</div>;
  }
}

2. 逻辑复用

解决同一个问题的三种方式

问题:如何让多个组件复用同一套逻辑(比如检查登录、加载数据)?

  • HOC 的解决方式

    • 层层包装:每个功能加一个盒子,比如: 原组件 → 套登录检查盒 → 套数据加载盒 → 最终组件。 → 缺点:盒子太多,拆起来麻烦(嵌套过深),还可能盖住原来的标签(props 冲突)。
  • Hooks 的解决方式

    • 直接调用工具:在组件内部用 useLoginCheck()useDataLoading(),像拼积木一样组合功能。 → 优点:不用包装,直接组装,代码更清晰。
  • 基类继承的方式

// 函数式组件:自定义 Hook
function useCounter(initialValue) {
  const [count, setCount] = useState(initialValue);
  const increment = () => setCount(c => c + 1);
  return { count, increment };
}

// 类组件:高阶组件(HOC)
function withCounter(Component) {
  return class extends React.Component {
    state = { count: 0 };
    increment = () => this.setState({ count: this.state.count + 1 });
    render() {
      return <Component count={this.state.count} increment={this.increment} />;
    }
  };
}
// 在函数式组件中使用
import React from 'react';
import useCounter from './useCounter';

function CounterComponent() {
  // 直接调用 Hook,解构出状态和方法
  const { count, increment } = useCounter(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
    </div>
  );
}
 // 在类组件中使用
import React from 'react';
import withCounter from './withCounter';

class CounterComponent extends React.Component {
  render() {
    // 通过 this.props 访问 HOC 注入的 count 和 increment
    const { count, increment } = this.props;

    return (
      <div>
        <p>Count: {count}</p>
        <button onClick={increment}>+1</button>
      </div>
    );
  }
}

// 用 HOC 包装组件,增强功能
export default withCounter(CounterComponent);
对比总结
方案基类继承HOCHooks
复用方式继承基类包装组件直接调用函数
耦合性高(子类依赖基类)低(通过 props 注入)无(逻辑与组件独立)
动态组合不支持(单继承限制)支持(多 HOC 嵌套)支持(多 Hook 调用)
适用场景简单复用、类组件类组件、需组合功能的场景函数式组件、现代 React 项目
未来兼容性不推荐(React 不鼓励继承)逐步被 Hooks 替代官方推荐的未来方向
如何选择?
  1. 如果你的项目全是类组件且逻辑简单

    1. 基类继承可以快速实现复用,但需注意避免基类膨胀和过度耦合。

  2. 如果需要动态组合多个功能

    1. 使用 HOC 或 Hooks:HOC 适合类组件,Hooks 适合函数式组件。

  3. 如果是新项目或计划迁移到函数式组件

    1. 优先使用 Hooks:更简洁、灵活且符合 React 最佳实践。

  4. 如果遇到多重继承需求

    1. 必须放弃基类继承,改用 HOC 或 Hooks 的组合模式。

举个实际例子

需求:多个组件需要复用 日志记录权限校验 功能。

  1. 基类继承的局限

    1. // 基类 BaseComponent 提供日志和权限方法
      class ComponentA extends BaseComponent { /* 使用日志和权限 */ }
      class ComponentB extends BaseComponent { /* 使用日志和权限 */ }
      
      // 问题:如果某个组件只需要日志,不需要权限,无法剔除基类中的冗余方法。
      
  2. HOC 的灵活组合

    1. // 定义独立的 HOC
      const withLogger = (Component) => { /* 注入日志方法 */ };
      const withAuth = (Component) => { /* 注入权限方法 */ };
      
      // 按需组合
      const ComponentA = withLogger(MyComponent); // 仅日志
      const ComponentB = withAuth(withLogger(MyComponent)); // 日志 + 权限
      
  3. Hooks 的直接调用

    1. function ComponentA() {
        const { log } = useLogger();
        // 仅使用日志
      }
      
      function ComponentB() {
        const { log } = useLogger();
        const { checkAuth } = useAuth();
        // 组合使用日志和权限
      }
      

总结

函数式组件和类组件在功能上已趋于等价,但函数式组件凭借 Hooks 在代码简洁性、逻辑复用性、未来兼容性上更具优势。建议开发者优先掌握函数式组件的使用,同时了解类组件以维护旧代码。