如果你是一位前端开发者,又在某些机会下阅读过一些 Java 代码,可能会在后者中看到一种类似 ES6 语法中箭头函数的写法
代码语言:javascript复制(String a, String b) -> a.toLowerCase() b.toLowerCase();
这种从 Java 8 后出现的 lambda 表达式,在 C / Python 中都有出现,它比传统的 OOP 风格代码更紧凑;虽然 Java 中的这种表达式本质上还是一个生成类实例的函数式接口(functional interface)语法糖,但无论其简洁的写法,还是处理不可变值并映射成另一个值的行为,都是典型的函数式编程(FP - functional programming)特征。
1992 年的图灵奖得主 Butler Lampson 有一个著名的论断:
All problems in computer science can be solved by another level of indirection 计算机科学中的任何问题都可以通过增加一个间接层次来解决
这句话中的“间接层次”常被翻译成“抽象层”,尽管有人曾争论过其严谨性,但不管怎么翻译都还说得通。无论如何,OOP 语言拥抱 FP,都是编程领域日益融合并重视函数式编程的直接体现,也印证了通过引入另一个间接层次来解决实际问题的这句“软件工程基本定理”。
还有另一句同样未必那么严谨的流行说辞是:
OOP 是对数据的抽象,而 FP 用来抽象行为
不同于面向对象编程中,通过抽象出各种对象并注重其间的解耦问题等;函数式编程聚焦于最小的单项操作,将复杂任务变成一次次 f(x) = y 式的函数运算叠加。函数是 FP 中的一等公民(First-class object),可以被当成函数参数或被函数返回。
同时在 FP 中,函数应该不依赖或影响外部状态,这意味着对于给定的输入,将产生相同的输出 -- 这也就是 FP 中常常使用“不可变(immutable)”、“纯函数(pure)”等词语的缘由;如果再把前面提过的 “lambda 演算”,以及 “curring 柯里化” 等挂在嘴边,你听上去就是个 FP 爱好者了。
以上这些概念及其相关的理论,集中诞生在 20 世纪前半叶,众多科学家对数理逻辑的研究收获了丰硕的成果;甚至现在热门的 ML、AI 等都受益于这些成果。比如当时大师级的美国波兰裔数学家 Haskell Curry,他的名字就毫不浪费地留在了 Haskell 语言和柯里化这些典型的函数式实践中。
React 函数式组件
如果使用过 jQuery / RxJS 时的“链式语法”,其实就可以算做 FP 中 monad 的实践;而近年来大多数前端开发者真正接触到 FP,一是从 ES6 中引入的 map / reduce 等几个函数式风格的 Array 实例方法,另一个就是从 React 中的函数式组件(FC - functional component)开始的。
React 中的函数式组件也常被叫做无状态组件(Stateless Component),更直观的叫法则是渲染函数(render function),因为写出来真的就是个用来渲染的函数而已:
代码语言:javascript复制const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
}
结合 TypeScript 的话,还可以使用 type 和 FC<propsType>
来对这个返回了 jsx 的函数约束入参:
type GreetingProps = {
name: string;
}
const Greeting:React.FC<GreetingProps> = ({ name }) => {
return <h1>Hello {name}</h1>
};
也可以用 interface 和范型,更灵活地定义 props 类型:
代码语言:javascript复制interface IGreeting<T = 'm' | 'f'> {
name: string;
gender: T
}
export const Greeting = ({ name, gender }: IGreeting<0 | 1>): JSX.Element => {
return <h1>Hello { gender === 0 ? 'Ms.' : 'Mr.' } {name}</h1>
};
Vue(2.x) 中的函数式组件
在 Vue 官网文档的【函数式组件】章节中,这样描述到:
代码语言:javascript复制...我们可以将组件标记为 functional,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。一个函数式组件就像这样:
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})
...
在 2.5.0 及以上版本中,如果你使用了[单文件组件],那么基于模板的函数式组件可以这样声明:
<template functional>
</template>
写过 React 并第一次阅读到这个文档的开发者,可能会下意识地发出 “啊这...” 的感叹,写上个 functional
就叫函数式了???
实际上在 Vue 3.x 中,你还真的能和 React 一样写出那种纯渲染函数的“函数式组件”,这个我们后面再说。
在目前更通用的 Vue 2.x 中,正如文档中所说,一个函数式组件(FC - functional component)就意味着一个没有实例(没有 this 上下文、没有生命周期方法、不监听任何属性、不管理任何状态)的组件。从外部看,它大抵也是可以被视作一个只接受一些 prop 并按预期返回某种渲染结果的 fc(props) => VNode
函数的。
并且,真正的 FP 函数基于不可变状态(immutable state),而 Vue 中的“函数式”组件也没有这么理想化 -- 后者基于可变数据,相比普通组件只是没有实例概念而已。但其优点仍然很明显:
- 因为函数式组件忽略了生命周期和监听等实现逻辑,所以渲染开销很低、执行速度快
- 相比于普通组件中的
v-if
等指令,使用 h 函数或结合 jsx 逻辑更清晰 - 更容易地实现高阶组件(HOC - higher-order component)模式,即一个封装了某些逻辑并条件性地渲染参数子组件的容器组件
- 可以通过数组返回多个根节点