导读:本期聚焦于追梦人创作的《React组件设计模式:函数组件与类组件该如何选择?》,敬请观看详情。函数组件借助Hooks已经能覆盖绝大多数类组件的场景,但两者在生命周期建模、错误边界、性能优化手段上仍有实质差异。选择策略并非非此即彼,而是取决于组件是否需要实例方法、是否依赖this、以及团队对代码一致性的要求。本文从渲染机制、状态更新粒度、复用逻辑的成本三个维度拆解函数组件与类组件的技术本质,给出可落地的选型清单,并指出在并发渲染与严格模式下类组件可能出现的额外渲染问题。通过对比代码示例,说明函数组件在逻辑抽离和副作用管理上的优势,以及类组件在需要暴露命令式接口或实现错误边界时依然不可替代的理由。读者将获得一套清晰的决策框架,避免在项目重构时陷入教条式争论。

React从2019年引入Hooks之后,函数组件与类组件的边界被彻底重写。但这并不意味着类组件已经退出历史舞台——错误边界、需要暴露实例方法的场景仍然依赖类组件。选择的核心不是语法偏好,而是组件在运行时是否需要持有一个可变实例,以及团队对逻辑内聚度的要求。下文从渲染机制、状态管理、性能特征三个角度展开,最后给出一套可落地的选型清单。

React组件设计模式:函数组件与类组件该如何选择?

一、渲染机制与实例模型:this到底带来了什么

类组件的执行模型建立在实例之上。每次使用<MyClassComponent />时,React会调用new操作创建组件实例,实例上保存着this.state、this.props以及各个生命周期方法。渲染过程只是调用实例的render方法返回JSX,更新状态必须通过this.setState触发,React随后重新执行render。整个生命周期内实例保持不变,因此开发者可以在类组件中定义任意实例属性,用来存储不需要触发渲染的数据。

函数组件则完全不同。它没有实例,每次渲染就是一次普通的函数调用,传入新的props,返回新的React元素。状态通过useState等Hooks存储在React内部的Fiber节点上,而不是组件实例上。函数执行完毕后局部变量全部销毁,只有被Hooks捕获的值通过闭包保留下来。这种模型让函数组件更接近纯函数,天然避免了this绑定带来的歧义,但也意味着无法在组件外部通过ref调用实例方法。

下面的示例对比了同样的窗口尺寸监听逻辑在两种写法下的代码组织差异:

class WindowSize extends React.Component {
  state = { width: window.innerWidth };
  componentDidMount() {
    window.addEventListener('resize', this.handleResize);
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize);
  }
  handleResize = () => {
    this.setState({ width: window.innerWidth });
  };
  render() {
    return <div>{this.state.width}px</div>;
  }
}
function WindowSize() {
  const [width, setWidth] = React.useState(window.innerWidth);
  React.useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  return <div>{width}px</div>;
}

类组件把副作用分散在componentDidMount和componentWillUnmount两个生命周期中,函数组件则把注册和清理写在同一个useEffect回调里,逻辑更加内聚。

二、状态逻辑复用:从生命周期到自定义Hook

类组件时代,复用有状态逻辑只能依赖高阶组件(HOC)或render props。这两种模式都会引入额外的组件层级,容易造成“包装地狱”,并且不同HOC之间的props命名可能冲突,TypeScript类型推断也会变得复杂。例如一个简单的数据请求逻辑,使用HOC需要创建一个withRequest函数,返回一个类组件,在内部管理loading和data状态,再通过props传给被包裹组件。

函数组件借助自定义Hook解决了这一痛点。自定义Hook本质上是一个以use开头的普通函数,内部可以使用useState、useEffect等Hooks,并返回任意数据。每个调用自定义Hook的组件都会获得独立的state和effect,互不干扰。下面是一个useFetch Hook的完整实现:

function useFetch(url) {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  React.useEffect(() => {
    let cancelled = false;
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(json => {
        if (!cancelled) {
          setData(json);
          setLoading(false);
        }
      });
    return () => { cancelled = true; };
  }, [url]);
  return { data, loading };
}

function UserList() {
  const { data, loading } = useFetch('/api/users');
  if (loading) return <p>加载中</p>;
  return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

可以看出,useFetch把请求状态、副作用和取消逻辑封装在一个函数里,组件内部只需一行调用即可获得data和loading。如果使用类组件实现同样功能,需要在componentDidMount发起请求、在componentDidUpdate处理url变化、在componentWillUnmount取消请求,逻辑被撕扯到多个生命周期中。

自定义Hook还可以自由组合。例如可以基于useFetch再封装出useUser、useArticle等业务Hook,或者将通用的防抖、节流逻辑抽离成useDebounce。这种分层复用在类组件中几乎无法优雅实现,因为生命周期方法无法像普通函数那样组合和调用。

三、性能特征与并发渲染下的差异

在性能优化方面,类组件通过继承React.PureComponent或手动实现shouldComponentUpdate来阻止不必要的渲染。但这种方式需要开发者明确知道哪些props或state发生了变化,容易遗漏嵌套对象的比较。函数组件则使用React.memo对props进行浅比较,再配合useMemo和useCallback缓存引用,可以将跳过渲染的粒度控制到单个值级别。

并发渲染(Concurrent Rendering)放大了两者的差异。React 18中,渲染过程可以被中断和恢复,类组件的生命周期方法(如componentDidMount、componentDidUpdate)可能被多次调用,因为它们依赖实例状态。函数组件虽然没有生命周期方法,但useEffect同样遵循“渲染后执行”的语义,在严格模式下会被双重调用来暴露副作用问题。区别在于,函数组件的依赖数组让React明确知道何时需要重新执行副作用,而类组件只能通过componentDidUpdate中的手动比较prevProps和this.props来实现类似效果。

下面是一个函数组件的性能优化示例,通过React.memo和useMemo避免子组件在父组件状态变化时重新渲染:

const ExpensiveChild = React.memo(function ExpensiveChild({ items }) {
  return <ul>{items.map(item => <li key={item.id}>{item.text}</li>)}</ul>;
});

function Parent() {
  const [count, setCount] = React.useState(0);
  const items = React.useMemo(() => [{ id: 1, text: '静态数据' }], []);
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>点击 {count}</button>
      <ExpensiveChild items={items} />
    </div>
  );
}

值得注意的是,类组件在需要实现错误边界时仍然不可替代。getDerivedStateFromError和componentDidCatch目前没有对应的Hook,因此任何需要捕获子组件渲染错误的场景都必须编写类组件。这也是类组件在未来一段时间内继续存在的核心理由之一。

四、选型决策框架与迁移策略

综合前面的分析,可以总结出一套实用的选型清单。以下情况应该使用类组件:实现错误边界、需要暴露命令式实例方法给父组件通过ref调用、依赖的第三方库只支持类组件、项目现有代码完全基于类组件且团队没有迁移计划。以下情况推荐使用函数组件:新建业务组件、需要复用有状态逻辑、需要精细控制副作用执行时机、团队已经启用Hooks规范并配置了eslint-plugin-react-hooks。

如果项目正在从类组件向函数组件迁移,切忌一次性重写所有组件。比较稳妥的策略是:先迁移纯展示组件,这类组件没有内部状态,重写成本低、风险小;再处理有状态组件,优先把组件拆分为容器组件和展示组件,容器组件用函数组件重写,展示组件保持不变。对于类组件中的生命周期逻辑,可以逐步抽取为自定义Hook,然后由新的函数组件调用。类组件本身无法直接使用Hook,但可以保留为错误边界,外层包裹函数组件。

最后需要强调的是,工具链对两类组件都有完整支持。React DevTools可以正常调试两种组件,TypeScript在函数组件中的类型推断通常更简单,因为不需要处理this的类型。团队层面的规范比个人偏好更重要:只要选型标准统一、代码评审严格执行,无论选择哪种组件风格,都能保证项目的可维护性。真正应该避免的是在同一个文件中混合多种模式,导致新人难以理解数据流和生命周期。

React组件设计模式函数组件类组件修改时间:2026-09-26 20:57:07

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0926/62286.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。