React 开发者对两种类型的组件类型应该很熟悉:类组件和函数组件。 “类”和“函数”这两个词自然会让人联想到面向对象编程(OOP)和函数式编程(FP)。
类组件与OOP相关吗?函数组件与FP相关吗?如果类组件与OOP相关,那么OOP原则(继承、封装、多态等)就能指导基于类的组件开发。同理,FP原则也可能影响函数组件的设计。换句话说,我们可以将这些编程范式的最佳实践直接应用到React项目中。
函数组件和函数式编程之间究竟是什么关系呢?本文将深入探讨这个问题。
编程范式与DSL
首先,我们需要明确一点:框架语法本质上是一种DSL(领域特定语言),专门为特定领域的开发而设计。
React就是一个用于构建用户界面的DSL。虽然不同平台使用不同的视图框架,例如:
- Web:ReactDOM
- 小程序:Taro
- 原生开发:字节跳动内部框架React Lynx
但这些框架通常遵循相同的DSL(React语法)。这个DSL并不依赖于任何特定的编程范式,而应该被视为一系列非常适合视图开发的语言特性集合。
因此,作为React DSL的一部分:
- 函数组件可以体现OOP原则。
- 类组件可以体现FP原则。
只要这些原则有利于视图开发,就可以将它们整合到DSL中。
例如,考虑以下由welcomeMessage和logoutButton组成的函数组件Header,它展现了OOP中组合优于继承的原则:
function Header(props) { return ( <div> <welcomeMessage name={props.name} /> <logoutButton onClick={props.onLogout} /> </div> ); }
同样,考虑一个类组件Cpn,其中状态计数并非通过突变(this.state.count)更新,而是通过使用不可变数据调用this.setState:
class Cpn extends React.Component { // ... onClick() { const count = this.state.count; this.setState({ count: count + 1 }); } render() { // ... } }
使用不可变数据体现了FP的原则。
因此,在分析任何React特性时,我们应该遵循以下三个步骤:
- React的核心开发理念是什么?
- 各种编程范式的哪些思想被用来实现这一理念?
- 这些思想如何在React中应用?
将此思维过程应用于函数组件和函数式编程之间的关系,我们发现:
- 函数组件是实现的结果(步骤3)。
- 函数式编程是一种编程范式(步骤2)。
这定义了它们的关系:函数组件是React中实现多种编程范式(主要是OOP和FP)的产物,过程中借鉴了一些FP的思想。函数组件不应该仅仅被视为React中函数式编程的体现。
函数组件的演变
让我们使用前面提到的三步思维过程来探索函数组件的演变。React的核心开发理念可以用以下公式表达:
UI = fn(snapshot);
要实现这一理念,需要两个关键要素:
- 数据快照
- 函数映射
在这里,来自FP的不可变数据更适合作为数据快照的载体。这就是为什么React中的状态是不可变的——状态的本质就是快照。
函数映射的载体没有具体要求。在React中,每次更新都会触发重新渲染,渲染过程本身就是函数映射过程。输入是props和state,输出是JSX。
相比之下,Vue组件更符合OOP原则。考虑这个Vue应用程序组件:
const app = { setup(initialProps) { const count = reactive({ count: 0 }); const add = () => { count.value++; }; return { count, add }; }, template: "...omitted" };
组件的setup方法在初始化时只执行一次。后续更新会在闭包内操作相同的数据,这对应于OOP中的实例的概念。
由于React对函数映射的载体没有特殊要求,所以类组件和函数组件都是可行的选择。
为什么函数组件取代了类组件?
许多人认为,通过Hook提高逻辑的可重用性是函数组件优于类组件的主要原因。然而,基于装饰器的类开发模型,特别是与TypeScript配合使用时,已被证明是逻辑重用的有效方法。
真正的原因在于函数组件能够更好地实现UI = fn(snapshot)的理念。
前面提到,公式中的快照代表的是状态的快照,在React中包括:
- 状态
- 属性 (props)
- 上下文
对于给定的组件,公式UI = fn(snapshot)确保相同的快照产生相同的输出(JSX)。然而,状态更新也可能触发副作用,例如数据获取或DOM操作。
在类组件中,这些副作用逻辑分散在各种生命周期方法中,使得React难以控制。然而,在函数组件中:
- 副作用仅限于useEffect。React确保在应用新的效果之前清除先前渲染的效果(通过useEffect的返回值)。
- ref的传播通过forwardRef等机制受到限制,限制了其潜在影响。
- 数据获取副作用由Suspense管理,如下所示:
function UserList({ id }) { const data = use(fetchUser(id)); // ... }
用法:
<Suspense fallback={<div>Loading...</div>}> <UserList id={1} /> </Suspense>
简而言之,函数组件确保副作用保持可控,从而为相同的快照输入提供一致的输出。这与FP的纯函数概念不谋而合,这也是函数组件成为React主流选择的原因。
结论
函数组件并非React中函数式编程的直接实现,而是最适合实现React核心理念的载体:UI = fn(snapshot)。React整合了各种编程范式的优秀思想,其中影响最大的是FP。最终,每一个设计选择都服务于整体理念。
我们是Leapcell,您托管Node.js项目的首选。
Leapcell是用于Web托管、异步任务和Redis的下一代无服务器平台:
多语言支持
- 使用Node.js、Python、Go或Rust进行开发。
免费部署无限个项目
- 只需支付使用费用——无请求,不收费。
无与伦比的成本效率
- 即用即付,无闲置费用。
- 示例:25美元支持694万个请求,平均响应时间为60毫秒。
简化的开发者体验
- 直观的用户界面,轻松设置。
- 完全自动化的CI/CD管道和GitOps集成。
- 实时指标和日志记录以获取可行的见解。
轻松的可扩展性和高性能
- 自动扩展,轻松处理高并发。
- 零运营开销——只需专注于构建。
在文档中探索更多内容!
在X上关注我们:@leapcellhq
阅读我们的博客