函数式编程时声明式编程的一种,思想是我需要什么样的输出,我需要什么样的输入
- 纯函数
就是一定的输入对应一定的输出,类似数学里的函数。
可以确保具有确定性:不纯的函数最直接的问题,就是不确定性(风险)—— 输出的行为是难以预测的(导致难调试,难复用,难维护,体现软件设计中的高内聚)
- 副作用
如果一个函数除了计算之外,还对它的执行上下文、执行宿主等外部环境造成了一些其它的影响,那么这些影响就是所谓的” 副作用”,如:
对外部的变量进行了修改
网络请求不可预知性
数据不可变
对象的不可变性(Immutability)是指在创建一个对象之后,不能直接修改该对象的状态。如果需要对该对象进行更改,则应该创建一个新的对象,而不是直接修改原有对象。
在 React 中,不可变性尤其重要,因为 React 通过对比前后状态(浅比较)来决定组件是否需要重新渲染。如果状态对象是不可变的,那么每次状态更新都会创建一个新的对象,React 可以快速判断状态是否发生变化,从而提高渲染性能。
可变数据使函数行为变得难以预测 - 不确定性
可变数据使函数复用成本变高
“不可变” 的核心是要变化,
当函数的入参是引用数据类型时,修改该数据的属性值必然会影响到外部环境。
要保证不可变是实践是拷贝:确保函数的外部数据是只读的
拷贝意味着重复,而重复往往伴随着冗余
(immer https://immerjs.github.io/immer/zh-CN/ )将拷贝操作精准化,将 “变与不变” 分离,确保只有变化的部分被处理,而不变的部分则将继续留在原地被共享
不可变数据结构通过永不更改状态对象来解决这个问题。与 Vue 不同的是,它会创建一个新对象,保留旧的对象未发生改变的一部分。在 JavaScript 中有多种不同的方式来使用不可变数据,但我们推荐使用 Immer 搭配 Vue,因为它使你可以在保持原有直观、可变的语法的同时,使用不可变数据。
// 这玩意相当于usestate
import { produce } from 'immer'
import { shallowRef } from 'vue'
export function useImmer(baseState) {
const state = shallowRef(baseState)
const update = (updater) => {
state.value = produce(state.value, updater)
}
return [state, update]
}
2
3
4
5
6
7
8
9
10
11
12
当且仅当写操作确实发生时,拷贝动作才会被执行
设计核心是按需拷贝
组合:
reduce:参数组合
compose:函数组合
科里化:curry 后的函数逐个传参至只剩下一个待传参数为止(最后一个入参最为下一个函数的入参)
柯里化说的是一个 n 元函数变成 n 个一元函数。
偏函数说的是一个 n 元函数变成一个 m (m < n) 元函数。
面相对象和函数式:
- 抽象:OOP 将数据与行为打包抽象为对象,对象是一等公民;而 FP 将行为抽象为函数,数据与行为是分离的,函数是一等公民。
- 代码重用:OOP 的核心在于继承,而 FP 的核心在于组合。
FP 求解,重行为、轻数据结构的场景
OOP 求解,重数据结构、轻行为的场景
结合二者优点,oop 中方法采用 fp 思想
Vue 中的副作用
视图变化的过程中 computed 就成为了副作用; Vue 提供了一个 API 来让你创建响应式副作用 watchEffect()
。
import { ref, watchEffect } from 'vue'
const A0 = ref(0)
const A1 = ref(1)
const A2 = ref()
watchEffect(() => {
// 追踪 A0 和 A1
A2.value = A0.value + A1.value
})
// 将触发副作用
A0.value = 2
2
3
4
5
6
7
8
9
10
11
12
13
A0 和 A1 的改动会被自动监控
使用一个响应式副作用来更改一个 ref 并不是最优解,事实上使用 computed 会更直观简洁:
import { ref, computed } from 'vue'
const A0 = ref(0)
const A1 = ref(1)
const A2 = computed(() => A0.value + A1.value)
A0.value = 2
2
3
4
5
6
7
react 在数据改变后产生的副作用有:
- 重新执行我们定义的函数(产生了新的闭包);
- 重新构建 fiber 树;
- 触发 渲染视图;
- 触发 effect 定义的副作用;