函数组件 和 函数式编程 有关系么?

大家好,我卡颂。

长期使用React的同学应该知道,React中存在两种组件:

  • Class Component,类组件

  • Function Component,函数组件

既然提到函数,那么很自然的,我们会进一步思考:

  • 类组件和OOP(面向对象编程)有关系么?

  • 函数组件和FP(函数式编程)有关系么?

毕竟,如果类组件和OOP有关,那么OOP中的思想(继承、封装、多态...)也能指导类组件的业务开发(函数组件与FP的关系同理)。换言之,我们可以直接用这些编程范式的最佳实践指导React项目开发。

那么,函数组件函数式编程究竟是什么关系呢?本文会围绕这个话题展开讲解。

欢迎围观朋友圈、加入人类高质量前端交流群,带飞

编程范式与DSL

首先,我们应该明确,框架语法 本质是一种DSL(领域相关语言),他是为了某个特定领域的开发量身定制的。

比如,React作为一款针对view开发DSL,虽然不同的view使用的框架不同,比如:

  • 对于web,框架为ReactDOM

  • 对于小程序,框架为Taro

  • 对于原生开发,字节内部有个叫React Lynx的框架

但这些框架都大体遵循同一套DSLReact语法),这套DSL并不属于某一种编程范式,而应该被视为不同编程范式中,更符合view开发的语言特性的集合

所以,作为React DSL的一部分,函数组件可以体现OOP的思想,类组件也能体现FP的思想。只要这些思想有利于view开发 ,就可以纳入DSL的语法中。

比如,下面的函数组件Header,是由WelcomeMessageLogoutButton组件组合而成,这是OOP中的组合优于继承思想:

js 复制代码
function Header(props) {
  return (
    <div>
      <WelcomeMessage name={props.name} />
      <LogoutButton onClick={props.onLogout} />
    </div>
  );
}

再比如,下面的类组件Cpn中,要改变状态count,并不是通过突变(类似this.state.count++),而是调用this.setState,传入不可变数据:

js 复制代码
class Cpn extends React.Component {
  // ...
  onClick() {
    const count = this.state.count;
    this.setState({count: count + 1});
  }
  render() {
    // ...
  }
}

使用不可变数据 属于FP中的思想。

所以,当我们要深入了解某个React特性时,应该以如下顺序递进的思考:

  1. React的开发理念是什么?

  2. 为了实现这套理念,吸收了哪些编程范式中的思想

  3. 这些思想如何在React中落地

如果我们用上述思考过程研究函数组件与函数式编程的关系,会发现:

  • 函数组件属于落地的产物(上述思考的第三步)

  • 函数式编程属于编程范式(上述思考的第二步)

这就是两者的关系 ------ 函数组件属于多种编程范式(主要是OOPFP)在React中最终的落地产物,其中借鉴了一部分FP的思想。

我们不应该将函数组件单纯视为FPReact中的具象体现。

那么,函数组件究竟是如何演进而来的呢?

函数组件的演进

让我们按照上述三步演进顺序思考。首先,React的开发理念践行了如下公式(即:UI是数据快照经过函数映射而来):

js 复制代码
UI = fn(snapshot)

要落地这个理念,有两个要素需要实现:

  • 数据快照

  • 函数映射

在这里,FP不可变数据 更适合作为数据快照 的载体,所以React中状态是不可变的,因为状态的本质是快照。

函数映射 的载体则没有特殊要求。在React中,每次触发更新,所有组件都会重新renderrender的过程就是函数映射 的过程,输入是propsstate,输出是JSX

React相对的,Vue中组件则更符合OOP的理念,考虑如下App组件:

js 复制代码
const App = {
  setup(initialProps) {
    const count = reactive({count: 0})
    const add = () => { count.value++ }
    return {count, add}
  }
  template: "...省略"
}

组件的setup方法只会在初始化时执行一次,后续触发更新时操作的都是同一个闭包中的数据。这里面的闭包就是OOP思想中的实例。

既然React函数映射的载体没有特殊要求,那么类组件、函数组件都是可以的。

那为什么函数组件最终替代了类组件成为React开发的主流呢?很多同学认为函数组件的Hooks可以更好的复用逻辑这一点,是函数组件优于类组件的主要原因。

但实际上,基于装饰器的类开发模式早已被验证是优秀的逻辑复用模式,类组件配合TS装饰器的模式是行得通的。

主要原因还是 ------ 函数组件能够更好的落地UI = fn(snapshot)这一理念。

刚才说过,公式中的snapshot快照 的含义。在React中,快照主要包括三类数据:

  • state

  • props

  • context

对于同一个组件,根据公式UI = fn(snapshot),相同的快照输入应该获得相同输出(JSX)。

但状态更新也可能触发副作用 ,比如请求数据、操作DOM...

在类组件中,这些副作用 逻辑被分散在各个生命周期钩子函数中,React无法掌控。

而在函数组件中:

  • 副作用受限在useEffect中。每次renderReact都会保证上次的副作用效果已经被清除(通过useEffect回调的返回值函数)

  • ref的传播也需要借由forwardRef,这进一步限制了ref可能的影响范围

  • 数据请求的副作用被交给Suspense处理,考虑下面组件:

js 复制代码
function UserList({id}) {
  // 异步请求数据
  const data = use(fetchUser(id));
  
  // ...
}

使用时:

html 复制代码
<Suspense fallback={<div>加载中...</div>}>
  <UserList id={1}/>
</Suspense>

总而言之,使用函数组件时,所有副作用都处于一种受到管控 的状态,可以尽可能保证每次更新时相同的快照输入,获得相同的JSX输出 ,所以函数组件在React中才会发扬光大。

同时,这也契合了FP中的纯函数思想。

总结

函数组件 并不是函数式编程React中的具体实现,而是React的设计理念UI = fn(snapshot)落地的最好载体。

React中,还吸收了其他编程范式中的优秀思想。FP只是其中影响React最深的一种。毕竟,一切落地都是为了践行最初的设计理念。

相关推荐
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端
爱敲代码的小鱼15 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax