Skip to content

类组件和函数组件的区别

类组件一直是构建有状态和复杂组件的主要方式。在 React 16.8 引入 Hooks 之后,函数组件变得空前强大,并逐渐成为社区的主流选择。

类组件

类组件都是使用class的写法,不能使用hooks,代码量更多,有完整的生命周期,内部方法需要绑定this

jsx
import React, { Component } from 'react';

class Counter extends Component {
  // --- 挂载阶段 ---

  // 1. 构造函数:组件被创建时首先调用
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      data: null,
    };
    console.log(' constructor() - 组件初始化');
  }

  // 3. 组件挂载后:组件已经被渲染到DOM中
  componentDidMount() {
    console.log('componentDidMount() - 组件已挂载到DOM');
    console.log('适合执行:API请求、添加事件监听等');
    // 模拟API请求
    this.timerID = setTimeout(() => {
      this.setState({ data: '从服务器获取的数据' });
      console.log('componentDidMount() - 异步数据已获取');
    }, 2000);
  }


  // --- 更新阶段 ---

  // 1. 是否应该更新?:在接收到新的props或state后,但在重新渲染前调用
  shouldComponentUpdate(nextProps, nextState) {
    console.log('更新shouldComponentUpdate()');
    // 可以在此进行性能优化,如果返回 false,后续的 render 和 componentDidUpdate 都不会执行
    return true;
  }

  // 3. 组件更新后:在组件更新并渲染到DOM后立即调用
  componentDidUpdate(prevProps, prevState) {
    console.log('更新componentDidUpdate()');
    // 可以在此根据 props 的变化执行副作用,例如当用户ID变化时重新请求数据
    if (this.props.user !== prevProps.user) {
      console.log('user prop 发生变化,可以执行新的API请求');
    }
  }

  // --- 卸载阶段 ---

  // 组件即将卸载:在组件从DOM中移除之前调用
  componentWillUnmount() {
    console.log('componentWillUnmount() - 组件即将卸载');
    console.log('适合执行:清理定时器、取消网络请求、移除事件监听');
    clearTimeout(this.timerID); // 清理在 componentDidMount 中创建的定时器
  }


  // --- 通用方法 ---

  // 点击事件处理函数
  handleIncrement = () => {
    this.setState(prevState => ({
      count: prevState.count + 1,
    }));
  };

  // 2. 渲染:无论是挂载还是更新,都需要这个方法来返回UI
  render() {
    console.log('render() - 正在渲染UI');
    return (
      <div style={{ border: '2px solid steelblue', padding: '10px', marginTop: '15px' }}>
        <h3>LifecycleLogger 组件</h3>
        <p>传入的用户 Prop: {this.props.user}</p>
        <p>内部的计数 State: {this.state.count}</p>
        <p>获取的数据: {this.state.data || '加载中...'}</p>
        <button onClick={this.handleIncrement}>增加 Count</button>
      </div>
    );
  }
}

export default Counter;
import React, { Component } from 'react';

class Counter extends Component {
  // --- 挂载阶段 ---

  // 1. 构造函数:组件被创建时首先调用
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      data: null,
    };
    console.log(' constructor() - 组件初始化');
  }

  // 3. 组件挂载后:组件已经被渲染到DOM中
  componentDidMount() {
    console.log('componentDidMount() - 组件已挂载到DOM');
    console.log('适合执行:API请求、添加事件监听等');
    // 模拟API请求
    this.timerID = setTimeout(() => {
      this.setState({ data: '从服务器获取的数据' });
      console.log('componentDidMount() - 异步数据已获取');
    }, 2000);
  }


  // --- 更新阶段 ---

  // 1. 是否应该更新?:在接收到新的props或state后,但在重新渲染前调用
  shouldComponentUpdate(nextProps, nextState) {
    console.log('更新shouldComponentUpdate()');
    // 可以在此进行性能优化,如果返回 false,后续的 render 和 componentDidUpdate 都不会执行
    return true;
  }

  // 3. 组件更新后:在组件更新并渲染到DOM后立即调用
  componentDidUpdate(prevProps, prevState) {
    console.log('更新componentDidUpdate()');
    // 可以在此根据 props 的变化执行副作用,例如当用户ID变化时重新请求数据
    if (this.props.user !== prevProps.user) {
      console.log('user prop 发生变化,可以执行新的API请求');
    }
  }

  // --- 卸载阶段 ---

  // 组件即将卸载:在组件从DOM中移除之前调用
  componentWillUnmount() {
    console.log('componentWillUnmount() - 组件即将卸载');
    console.log('适合执行:清理定时器、取消网络请求、移除事件监听');
    clearTimeout(this.timerID); // 清理在 componentDidMount 中创建的定时器
  }


  // --- 通用方法 ---

  // 点击事件处理函数
  handleIncrement = () => {
    this.setState(prevState => ({
      count: prevState.count + 1,
    }));
  };

  // 2. 渲染:无论是挂载还是更新,都需要这个方法来返回UI
  render() {
    console.log('render() - 正在渲染UI');
    return (
      <div style={{ border: '2px solid steelblue', padding: '10px', marginTop: '15px' }}>
        <h3>LifecycleLogger 组件</h3>
        <p>传入的用户 Prop: {this.props.user}</p>
        <p>内部的计数 State: {this.state.count}</p>
        <p>获取的数据: {this.state.data || '加载中...'}</p>
        <button onClick={this.handleIncrement}>增加 Count</button>
      </div>
    );
  }
}

export default Counter;
生命周期方法调用时机主要用途
constructor()组件实例创建时初始化 state、绑定 this
render()挂载和更新时返回JSX,描述UI
componentDidMount()组件挂载到DOM后网络请求、DOM操作、添加订阅
componentDidUpdate()state或props更新后基于props变化执行副作用
componentWillUnmount()组件从DOM移除前清理定时器、取消订阅、移除事件监听器
shouldComponentUpdate()render()前,接收新props或state时性能优化,避免不必要的渲染

函数组件

函数组件就简单很多了,代码简洁,加上hooks的引入可以处理复杂的逻辑,没有this关键字

jsx
import React, { useState, useEffect } from 'react';

function HooksLogger({ user }) {
  // 使用 useState 来管理 count 和 data 状态
  // 相当于类组件 constructor 中的 this.state
  const [count, setCount] = useState(0);
  const [data, setData] = useState(null);

  console.log('render() - 函数组件正在渲染UI');

  // --- 使用 useEffect 处理副作用 ---

  // 1. 模拟 componentDidMount
  // 第二个参数是空数组 [],表示这个 effect 只在组件首次挂载后执行一次。
  useEffect(() => {
    console.log('useEffect (mount) - 组件已挂载到DOM');
    console.log('适合执行:API请求、添加事件监听等');

    // 模拟API请求
    const timerID = setTimeout(() => {
      setData('从服务器获取的数据');
      console.log('useEffect (mount) - 异步数据已获取');
    }, 2000);

    // 清理函数:将在组件卸载时执行,模拟 componentWillUnmount
    return () => {
      console.log('useEffect (unmount) - 组件即将卸载');
      console.log('适合执行:清理定时器、取消网络请求、移除事件监听');
      clearTimeout(timerID);
    };
  }, []); //空数组是关键!

  // 2. 模拟 componentDidUpdate
  // 这个 effect 依赖于 `user` 和 `count`。当它们任何一个发生变化时,effect 都会重新执行。
  useEffect(() => {
    // 为了避免在挂载时也打印“更新”日志,可以使用一个 ref 来判断是否是首次渲染
    console.log('useEffect (update) - 组件已更新');

    // 示例:可以根据 user 的变化执行操作
    if (user) {
      document.title = `当前用户: ${user}`;
    }
  }, [user, count]); //依赖项数组是关键!


  // --- 事件处理函数 ---
  const handleIncrement = () => {
    setCount(prevCount => prevCount + 1);
  };

  return (
    <div style={{ border: '2px solid mediumseagreen', padding: '10px', marginTop: '15px' }}>
      <h3>HooksLogger 组件</h3>
      <p>传入的用户 Prop: {user}</p>
      <p>内部的计数 State: {count}</p>
      <p>获取的数据: {data || '加载中...'}</p>
      <button onClick={handleIncrement}>增加 Count</button>
    </div>
  );
}

export default HooksLogger;
import React, { useState, useEffect } from 'react';

function HooksLogger({ user }) {
  // 使用 useState 来管理 count 和 data 状态
  // 相当于类组件 constructor 中的 this.state
  const [count, setCount] = useState(0);
  const [data, setData] = useState(null);

  console.log('render() - 函数组件正在渲染UI');

  // --- 使用 useEffect 处理副作用 ---

  // 1. 模拟 componentDidMount
  // 第二个参数是空数组 [],表示这个 effect 只在组件首次挂载后执行一次。
  useEffect(() => {
    console.log('useEffect (mount) - 组件已挂载到DOM');
    console.log('适合执行:API请求、添加事件监听等');

    // 模拟API请求
    const timerID = setTimeout(() => {
      setData('从服务器获取的数据');
      console.log('useEffect (mount) - 异步数据已获取');
    }, 2000);

    // 清理函数:将在组件卸载时执行,模拟 componentWillUnmount
    return () => {
      console.log('useEffect (unmount) - 组件即将卸载');
      console.log('适合执行:清理定时器、取消网络请求、移除事件监听');
      clearTimeout(timerID);
    };
  }, []); //空数组是关键!

  // 2. 模拟 componentDidUpdate
  // 这个 effect 依赖于 `user` 和 `count`。当它们任何一个发生变化时,effect 都会重新执行。
  useEffect(() => {
    // 为了避免在挂载时也打印“更新”日志,可以使用一个 ref 来判断是否是首次渲染
    console.log('useEffect (update) - 组件已更新');

    // 示例:可以根据 user 的变化执行操作
    if (user) {
      document.title = `当前用户: ${user}`;
    }
  }, [user, count]); //依赖项数组是关键!


  // --- 事件处理函数 ---
  const handleIncrement = () => {
    setCount(prevCount => prevCount + 1);
  };

  return (
    <div style={{ border: '2px solid mediumseagreen', padding: '10px', marginTop: '15px' }}>
      <h3>HooksLogger 组件</h3>
      <p>传入的用户 Prop: {user}</p>
      <p>内部的计数 State: {count}</p>
      <p>获取的数据: {data || '加载中...'}</p>
      <button onClick={handleIncrement}>增加 Count</button>
    </div>
  );
}

export default HooksLogger;
核心思想按功能划分: 将相关逻辑聚合在同一个 useEffect 中。一个 useEffect 负责一个功能的所有方面(设置、更新、清理)。
状态管理useState,可以是任何数据类型,状态和其更新函数是独立的,更灵活。
副作用统一使用 useEffect Hook。通过依赖项数组 [] 控制执行时机。
代码简洁性代码更少,更易读,没有 this 的困扰,逻辑更内聚。

程序员小洛文档