说说对 React 中类组件和函数组件的理解?有什么区别?

一、是什么

在 React 中,组件是构建 UI 的核心抽象。历史上,React 提供了两种主要的组件定义方式:

  • 类组件(Class Component):使用 ES6 的 class 语法,通过继承 React.Component 创建
  • 函数组件(Function Component):使用普通函数或箭头函数,接收 props 返回 JSX

在 React 16.8 之前,函数组件被称为"无状态组件",只能用来做纯展示。Hooks 的引入彻底改变了这一局面,使函数组件获得了完整的状态管理和副作用处理能力,并成为 React 官方推荐的组件形式。

二、语法差异

类组件

import React, { Component } from 'react';

interface GreetingProps {
  name: string;
}

interface GreetingState {
  count: number;
}

class Greeting extends Component<GreetingProps, GreetingState> {
  state: GreetingState = { count: 0 };

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

  render() {
    return (
      <div>
        <h1>你好,{this.props.name}</h1>
        <p>点击次数:{this.state.count}</p>
        <button onClick={this.handleClick}>点击</button>
      </div>
    );
  }
}

函数组件

import { useState } from 'react';

interface GreetingProps {
  name: string;
}

function Greeting({ name }: GreetingProps) {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>你好,{name}</h1>
      <p>点击次数:{count}</p>
      <button onClick={() => setCount(c => c + 1)}>点击</button>
    </div>
  );
}

三、状态管理

类组件的状态管理

类组件通过 this.statethis.setState 管理状态。setState 在事件处理中是批量异步更新的,多次调用会合并。

class Form extends Component<{}, { name: string; email: string }> {
  state = { name: '', email: '' };

  handleChange = (field: string, value: string) => {
    this.setState({ [field]: value } as any);
  };

  handleSubmit = () => {
    // this.state 始终指向最新的状态对象
    console.log(this.state);
  };

  render() {
    return (
      <form onSubmit={e => { e.preventDefault(); this.handleSubmit(); }}>
        <input value={this.state.name} onChange={e => this.handleChange('name', e.target.value)} />
        <input value={this.state.email} onChange={e => this.handleChange('email', e.target.value)} />
        <button type="submit">提交</button>
      </form>
    );
  }
}

函数组件的状态管理

函数组件通过 useStateuseReducer 等 Hooks 管理状态。对于复杂状态逻辑,useReducer 提供了更清晰的管理方式。

import { useReducer } from 'react';

type Action =
  | { type: 'SET_FIELD'; field: string; value: string }
  | { type: 'RESET' };

interface FormState {
  name: string;
  email: string;
}

function formReducer(state: FormState, action: Action): FormState {
  switch (action.type) {
    case 'SET_FIELD':
      return { ...state, [action.field]: action.value };
    case 'RESET':
      return { name: '', email: '' };
    default:
      return state;
  }
}

function Form() {
  const [state, dispatch] = useReducer(formReducer, { name: '', email: '' });

  return (
    <form onSubmit={e => { e.preventDefault(); console.log(state); }}>
      <input
        value={state.name}
        onChange={e => dispatch({ type: 'SET_FIELD', field: 'name', value: e.target.value })}
      />
      <input
        value={state.email}
        onChange={e => dispatch({ type: 'SET_FIELD', field: 'email', value: e.target.value })}
      />
      <button type="submit">提交</button>
    </form>
  );
}

四、生命周期 vs Hooks

类组件的生命周期

类组件拥有明确的生命周期方法:

class DataFetcher extends Component<{ id: string }, { data: any; loading: boolean }> {
  state = { data: null, loading: true };

  componentDidMount() {
    this.fetchData();
  }

  componentDidUpdate(prevProps: { id: string }) {
    if (prevProps.id !== this.props.id) {
      this.fetchData();
    }
  }

  componentWillUnmount() {
    // 清理工作,如取消请求
  }

  async fetchData() {
    this.setState({ loading: true });
    const res = await fetch(`/api/data/${this.props.id}`);
    const data = await res.json();
    this.setState({ data, loading: false });
  }

  render() {
    if (this.state.loading) return <p>加载中...</p>;
    return <pre>{JSON.stringify(this.state.data, null, 2)}</pre>;
  }
}

函数组件的 Hooks

函数组件使用 useEffect 统一处理副作用,不再按生命周期阶段拆分逻辑:

import { useState, useEffect } from 'react';

function DataFetcher({ id }: { id: string }) {
  const [data, setData] = useState<any>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);

    fetch(`/api/data/${id}`)
      .then(res => res.json())
      .then(result => {
        if (!cancelled) {
          setData(result);
          setLoading(false);
        }
      });

    return () => {
      cancelled = true;
    };
  }, [id]);

  if (loading) return <p>加载中...</p>;
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

Hooks 的一个核心优势是相关逻辑聚合在一起,而不是按生命周期阶段分散。类组件中 componentDidMountcomponentWillUnmount 里的配对逻辑可能隔得很远,Hooks 的清理函数就在 effect 内部。

五、this 绑定问题

类组件中 this 的指向是一个常见的坑:

class BuggyButton extends Component {
  state = { count: 0 };

  // ❌ 普通方法,this 指向可能丢失
  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  // ✅ 箭头函数属性,this 自动绑定
  handleClickSafe = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <>
        {/* ❌ 运行时报错:Cannot read properties of undefined */}
        <button onClick={this.handleClick}>Buggy</button>
        {/* ✅ 正常工作 */}
        <button onClick={this.handleClickSafe}>Safe</button>
        {/* ✅ 也可以在 render 中绑定,但每次渲染创建新函数 */}
        <button onClick={this.handleClick.bind(this)}>Bind</button>
      </>
    );
  }
}

函数组件完全没有 this 的概念,根本不存在绑定问题:

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

  const handleClick = () => {
    setCount(c => c + 1);
  };

  return <button onClick={handleClick}>{count}</button>;
}

六、Capture 语义——函数组件捕获渲染时的值

这是函数组件和类组件之间一个非常重要的行为差异。函数组件每次渲染都会捕获当次渲染的 props 和 state 值,而类组件的 this.propsthis.state 始终指向最新值。

// 类组件:this.props 指向最新值
class ClassProfilePage extends Component<{ user: string }> {
  showMessage = () => {
    alert('当前用户:' + this.props.user);
  };

  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };

  render() {
    return <button onClick={this.handleClick}>查看用户</button>;
  }
}

如果在点击后 3 秒内切换了用户,类组件会弹出新用户的名字,因为 this.props 是可变的引用。

// 函数组件:捕获渲染时的值
function FunctionProfilePage({ user }: { user: string }) {
  const showMessage = () => {
    alert('当前用户:' + user);
  };

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return <button onClick={handleClick}>查看用户</button>;
}

函数组件会弹出点击时的用户名,因为闭包捕获了那次渲染的 user 值。这种行为通常更符合用户预期,也更容易推理。

如果确实需要在函数组件中读取最新值,可以使用 useRef

function ProfilePage({ user }: { user: string }) {
  const latestUser = useRef(user);
  latestUser.current = user;

  const handleClick = () => {
    setTimeout(() => {
      alert('最新用户:' + latestUser.current);
    }, 3000);
  };

  return <button onClick={handleClick}>查看用户</button>;
}

七、性能差异

从性能角度看,两者的差异主要体现在:

  1. 包体积:函数组件编译后代码更少,没有类继承的额外开销
  2. 实例化:类组件每次创建都需要 new 一个实例,函数组件只是一次函数调用
  3. 优化潜力:React Compiler(React 19+)主要针对函数组件进行自动记忆化优化
// React Compiler 可以自动优化函数组件
// 开发者无需手动添加 useMemo / useCallback
function TodoList({ todos, filter }: { todos: Todo[]; filter: string }) {
  const filtered = todos.filter(t => t.text.includes(filter));

  return (
    <ul>
      {filtered.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </ul>
  );
}

八、逻辑复用

类组件时代的复用方式

类组件时代主要通过高阶组件(HOC)和 Render Props 实现逻辑复用,两者都存在"嵌套地狱"问题:

// HOC 嵌套
export default withRouter(withTheme(withAuth(MyComponent)));

// Render Props 嵌套
<ThemeContext.Consumer>
  {theme => (
    <AuthContext.Consumer>
      {auth => (
        <MouseTracker>
          {mouse => <MyComponent theme={theme} auth={auth} mouse={mouse} />}
        </MouseTracker>
      )}
    </AuthContext.Consumer>
  )}
</ThemeContext.Consumer>

函数组件的自定义 Hooks

函数组件通过自定义 Hooks 实现逻辑复用,代码扁平且直观:

function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  useEffect(() => { /* 认证逻辑 */ }, []);
  return { user, isLoggedIn: !!user };
}

function useTheme() {
  return useContext(ThemeContext);
}

function useMousePosition() {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const handler = (e: MouseEvent) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', handler);
    return () => window.removeEventListener('mousemove', handler);
  }, []);
  return pos;
}

function MyComponent() {
  const { user } = useAuth();
  const theme = useTheme();
  const mouse = useMousePosition();
  // 扁平结构,逻辑清晰
  return <div>...</div>;
}

九、对比总结

维度类组件函数组件
语法class + extends函数声明/表达式
状态this.state + setStateuseState / useReducer
副作用生命周期方法useEffect / useLayoutEffect
this 绑定需要手动处理无 this 概念
值捕获引用最新值 (this.props)捕获渲染时的值(闭包)
逻辑复用HOC / Render Props自定义 Hooks
错误边界支持不支持(截至 React 19)
代码量较多较少
未来优化有限React Compiler 重点优化对象

十、行业趋势与建议

React 官方文档自 2023 年起已全面使用函数组件作为示例。社区主流库(React Router、React Query、Zustand 等)也全部基于 Hooks API 设计。

实践建议:

  1. 新项目一律使用函数组件 + Hooks
  2. 老项目可以逐步将类组件迁移为函数组件
  3. Error Boundary 是目前唯一必须使用类组件的场景
  4. 面试中需要理解两者区别,但日常开发以函数组件为主
// 2024+ 年的标准 React 组件写法
import { useState, useEffect, useCallback } from 'react';

interface Props {
  userId: string;
}

function UserProfile({ userId }: Props) {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    let active = true;
    fetchUser(userId).then(data => {
      if (active) setUser(data);
    });
    return () => { active = false; };
  }, [userId]);

  if (!user) return <Skeleton />;

  return (
    <section>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </section>
  );
}

十一、总结

类组件和函数组件在功能上已经几乎等价,但函数组件在语法简洁性、逻辑复用能力、值捕获语义和未来优化潜力上都具有明显优势。理解两者的差异——特别是 Capture 语义和 this 绑定问题——是 React 开发者的必备知识。在新项目中应当毫不犹豫地选择函数组件 + Hooks 方案。