天命人!让我们由浅入深,了解formily/reactive由浅入深,讲解formily/reactive 一、背景知识
由浅入深,讲解formily/reactive
一、背景知识
1.1、一句话描述reactive
reactive 运用了设计模式的发布订阅模式,目的是为了让数据的变化后的视图驱动,变得更加精准和颗粒度更加小(更新范围完全受控!)
1.2、了解reactive有什么意义?
formily是针对form表单的的成熟解决方案;
-
优点:
-
高度集成化
-
语法简单
-
性能非常好!
-
-
缺点
- 过度集成化,出现问题很难定位
学习reactive,是为了解其背后原理,方便遇到问题后能够站在原理层面快速分析定位!
1.3、核心概念熟悉
::: [幼苗]必看
observable
一句话形容:observable(obj) 是为了让obj 偷偷的创建一个看不见的收集池(Collection)!让后续obj的读操作,都能收集reaction(reactive可被收集的载体)
reaction
可被收集的载体,数据结构如下
export type Reaction = ((...args: any[]) => any) & {
_boundary?: number // 防止track内部 套track!后续会详细讲解
_name?: string // 标记,方便定位
_isComputed?: boolean // 标记是否为computed类型的reaction
_dirty?: boolean // 数据是否已经更新过
_context?: any // 大部分情况指向 target
_disposed?: boolean // 是否已经被卸载(所有的收集)
_property?: PropertyKey // 绑定的 key
_computesSet?: ArraySet<Reaction> // compute 卸载专用,
_reactionsSet?: ArraySet<ReactionsMap> // 卸载专用!这里放的是,有多少个 map 里面 装了我!
_scheduler?: (reaction: Reaction) => void // 真实的更新的操作!不存在就直接 reaction函数执行!
_memos?: { // 类似于hooks 的useMemo ,方便在一些autorun 等函数中,保留记忆功能
queue: IMemoQueueItem[]
cursor: number
}
_effects?: { // 副作用, 根据deps,判断是否需要更新
queue: IEffectQueueItem[]
cursor: number
}
}
:::
一句话总结:++observable++为对象的每一层的 ++target 和 key++创建了一个++Collection++,然后在reaction operation过程中,读这些target的key的时候,会收集这些++reaction++;最后在写的时候执行这些reaction的++_scheduler++内容;
:::
[休假] reaction的种类
autorun
执行autorun的时候,会创建reaction,它只有++一个函数参数++,当你的诉求只是希望订阅数据,然后同步更新,autorun已经完全够用!
const obj = observable({ name: '' });
const dispose = autorun(() => {
console.log(obj.name);
}); // 返回的dispose用于卸载订阅
obj.name = 'leimao'; // console一次
obj.name = 'yaoran'; // console再一次
dispose(); // 卸载监听
obj.name = 'haha'; // 不会再打印了
autorun 的函数内部对其他数据进行了更改!不会有任何作用
const obj = observable({ name: '' });
const dispose = autorun(() => {
console.log(obj.name);
obj.name = 'leimao'; // 不起作用!
obj.name = 'yaoran'; // 不起作用!原因是什么?后续会给解释
}); // 返回的dispose用于卸载订阅
reaction
reaction的同名函数,和autorun不同的是它有3个参数,args1是函数,args依然是函数、args的options配置;
export function reaction1() {
const obj = observable({
age: 5,
level: 10,
});
const dispose = reaction(
() => {
console.log('重新计算'); // 立刻执行,因为reaction 内部会直接执行一次!
return obj.age + obj.level;
},
(newValue, oldValue) => {
console.log('value changed!');
},
{
equals: (oldValue, newValue) => {
return oldValue === newValue;
},
fireImmediately: false,
},
);
obj.age = 20;
// console '重新计算' 和 'value changed!'
batch(() => {
obj.age = 10;
obj.level = 20;
}); // console '重新计算'
// no value changed! 因为 obj.age + obj.level总值没有变!
dispose(); // 发生了卸载
obj.age = 100; // 没有任何console
}
Tracker
使用方式和autorun非常类似(实际源码实现的也很类似),不同的是它是一个class,而且没有autorun特有的memo和effect;然后就是tracker对于,收集函数和订阅函数进行了拆分
const obj = observable({name: ''})
const tracker = new Tracker(() => {
console.log('obj.name发生了更改');
}) // 订阅的函数
tracker.track(()=> {
obj.name;
}); // 执行依赖函数,目标是收集!
obs.name = 'leimao'; // console一次
tracker.dispose(); // 卸载监听
obs.name = 'haha'; // 不会再打印了
observe
它三个参数,分别是 对象和回调,以及配置是否支持深度监听!
他实际并不是真实的reaction,和autorun 、reaction、Tracker的实现方式不太一样,后面讲诉实现方式的时候会提到!
observable.computed
observable.computed 的基础使用是,参数是一个 callback,返回的是一个应用类型;
const pinxie = observable.computed(() => {
读compute值的时候,会进行双向绑定,如果存在reaction,会被computed收集!同时computed内部也有一个action, callback只会在读的时候会被执行! 当callback执行的时候,computed 会被内部的collection收集
const obj = observable({
name: 'dingeli',
age: 25,
});
const pinxie = observable.computed(() => {
console.log('重新计算了!'); // 执行一次
return `${obj?.name}:${obj.age}`; // obj?.name 收集了 computed的reaction
});
console.log(pinxie.value, '真实值'); // 打印了,但是由于没有reaction,所以computed没有收集到,无法形成收集链
obj.name = 'yaoran'; // 不会打印重新计算了!
只有在reaction 内部,才会形成收集链
const obj = observable({
name: 'dingeli',
age: 25,
});
const pinxie = observable.computed(() => {
console.log('重新计算了!'); // 执行一次
return `${obj?.name}:${obj.age}`;
});
autorun(() => {
console.log(pinxie.value, '真实值'); // 形成了收集链
});
obj.name = 'yaoran'; // 会打印重新计算了! computed的 schedule 1、标记dirty 2、通知自己搜集的重新运行;
当obj.name 设置了值,会通知搜集收集过的 autorun,autorun重新执行,就会读computed的value,就会重新计算了! :::
[羽毛笔]ArraySet
ArraySet 是最终存储reaction的数据结构,所有reaction都是直接存储在Arrayset里面的!基本可以理解为数组,多了部分定制的方法逻辑
export class ArraySet<T> {
value: T[]
forEachIndex = 0
constructor(value: T[] = []) {
this.value = value
}
add(item: T) { // 增加
if (!this.has(item)) {
this.value.push(item)
}
}
has(item: T) { // 是否存在
return this.value.indexOf(item) > -1
}
delete(item: T) { // 删除
const len = this.value.length
if (len === 0) return
if (len === 1 && this.value[0] === item) {
this.value = []
return
}
const findIndex = this.value.indexOf(item)
if (findIndex > -1) {
this.value.splice(findIndex, 1)
if (findIndex <= this.forEachIndex) {
this.forEachIndex -= 1
}
}
}
forEach(callback: (value: T) => void) { // 遍历处理
if (this.value.length === 0) return
this.forEachIndex = 0
for (; this.forEachIndex < this.value.length; this.forEachIndex++) {
callback(this.value[this.forEachIndex])
}
}
batchDelete(callback: (value: T) => void) { // 一次处理, 处理完直接删除
if (this.value.length === 0) return
this.forEachIndex = 0
for (; this.forEachIndex < this.value.length; this.forEachIndex++) {
const value = this.value[this.forEachIndex]
this.value.splice(this.forEachIndex, 1)
this.forEachIndex--
callback(value)
}
}
clear() { // 全部删除
this.value.length = 0
}
}
[本子]Collection
Collection 是用于收集reaction的收集池,对象的target 的每个key 都会有一个独立的 ArraySet 去收集reaction,格式如下:
最终的数据结构是一个二维map (RawReactionsMap),第一层是通过target获取的是自身下面所有key组成的一个map(innerMap),每个key里面存放的都是ArraySet!
二、原理剖析
2.1、运行总图
运行图可以看出,核心步骤主要是:
创建可搜集对象(proxy实现)
-
observable(obj)
-
observable.box(obj)
-
observable.ref(obj)
-
observable.deep(obj)
-
observable.shallow(obj)
-
observable.computed(obj)
运行逻辑,创建reaction
-
autorun
-
reaction
-
Tracker
-
observe
-
observable.computed
更改obj的属性,自动更新订阅的reaction
- runReactions
2.2、核心步骤的实现原理
2.2.1、创建可搜集对象(proxy实现)
const obj = observable({ name: '' }); 到底做了什么?
我们从observable开始去剖析:
export function observable<T extends object>(target: T): T {
return createObservable(null, null, target)
}
上面代码可以看出来observable = createObservable(null, null, target);
所以我们主要是要看createObservable到底做了啥!
我们可以带着问题去学习原理!
第一个问题:怎么实现收集!对象就像一棵树一样,每个属性我们都要去收集完全隔离的依赖!
能做到直接针对属性访问多拦截的只有proxy! object.defineProperty需要研发态强感知对象属性,很显然这不符合我们的预期!
我们实现一个简单版的 createObservable:(先不用纠结一些函数的细节,先理清楚它的基础架构)
const obj = createObservable(null, null, someObject )
export const createObservable = (
target: any,
key?: PropertyKey,
value?: any
) => {
const obj = value;
const proxy = new Proxy( obj, {
get(target, key, receiver) {
const current = getNowReaction(); // 获取当前reaction
addRawReactionsMap(target, key, current) // 收集依赖 (先不用纠结一些函数的细节,先理清楚它的基础架构)
const nextTarget = target[key];
return createObservable(target, key, nextTarget) // 每次get的时候,才会继续下一层观察能力的创建
},
set(target, key, value,receiver) {
const newValue = createObservable(target, key, value) // 重新建立收集能力
target[key] = newValue; // 设置值
runReactionsFromTargetKey({ // 通知收集过的依赖进行更新! (先不用纠结一些函数的细节,先理清楚它的基础架构)
target,
key,
value: newValue,
oldValue,
receiver,
type: 'set',
})
}
})
return proxy
}
出现了一个新问题来了:get的时候虽然,很可能之前已经建立过依赖了,不应该重复建立!我们改造一下吧!
思路通过WeakMap 存储建立过联系的数据,已经建立过的直接返回结果就可以了!
if(RawProxy.get(target)) {
return RawProxy.get(target); // 返回存在的proxy
}
const proxy = new Proxy(...); // 建立新的
RawProxy.set(target, proxy); // 保存关系
将思路和简单版结合起来!
export const createObservable = (
target: any,
key?: PropertyKey,
value?: any,
shallow?: boolean
) => {
const obj = value;
const proxy = new Proxy( obj, {
get(target, key, receiver) {
const current = getNowReaction(); // 获取当前reaction
addRawReactionsMap(target, key, current) // 收集依赖 (先不用纠结一些函数的细节,先理清楚它的基础架构)
const nextTarget = target[key];
const observableResult = RawProxy.get(nextTarget) // 【尝试判断是否已经建立过收集能力】新增✅
if (observableResult) { // 【已经存在收集能力】 新增✅
return observableResult // 新增✅
}
return createObservable(target, key, nextTarget) // 每次get的时候,才会继续下一层观察能力的创建
},
set(target, key, value,receiver) {
const newValue = createObservable(target, key, value) // 重新建立收集能力
target[key] = newValue; // 设置值
runReactionsFromTargetKey({ // 通知收集过的依赖进行更新! (先不用纠结一些函数的细节,先理清楚它的基础架构)
target,
key,
value: newValue,
oldValue,
receiver,
type: 'set',
})
}
});
// --------------分隔符------------------ 建立双向
ProxyRaw.set(proxy, target); // 【方便后续根据proxy 获取target ProxyRaw 是一个WeakMap】 新增✅
RawProxy.set(target, proxy) // 【方便后续根据 target 获取proxy。 RawProxy 是一个WeakMap】 新增✅
return proxy
}
当value 不是object,实际是不用建立收集能力的!所以应该增加一个判断!
export const createObservable = (
target: any,
key?: PropertyKey,
value?: any,
shallow?: boolean
) => {
if (typeof value !== 'object') return value // 新增✅ 必须是object,否则直接结束
... 中间内容省略了
return proxy
}
可以看到函数createObservable存在shallow属性,这意味着有一种需求,就是我们只需要target的表层需要做依赖收集!比如:
const obj = createObservable(null, null, { person: {name: ''} }, true)
autorun(()=> {
console.log(obj.person.name)
});
obj.person.name = 'leimao' // 不会触发autorun,因为是shallow模式
那我们要这么实现呢?答案也是建立一个WeakMap,专门收集浅层的监听,这样就能判断哪些数据是做shallow的!
RawShallowProxy.set(target, proxy); // 对象 和proxy设置起来!
::: 思路:第二层我们拦住它继续 createObservable操作!
当我们使用 proxy.person.name 的时候,首先读取的是 proxy.person;
proxy.person在 proxy的get当中,会继续使用 createObservable(target, key, target[key]);
这个时候,我们 RawShallowProxy.get(target) 可以获取到它是一个浅层观察!然后拦截他! :::
export const createObservable = (
target: any,
key?: PropertyKey,
value?: any,
shallow?: boolean
) => {
if (typeof value !== 'object') return value // 本次新增 ✅ 必须是obejct,否则直接结束
if (target) { // 本次新增 ✅
const isShallowParent = RawShallowProxy.get(target); // 查看是否是浅层 本次新增 ✅
if (isShallowParent) return value // shallowProxy 就是不要做任何操作? 为啥? 后面再看 本次新增 ✅
}
const obj = value;
const proxy = new Proxy( obj, {
get(target, key, receiver) {
const current = getNowReaction(); // 获取当前reaction
addRawReactionsMap(target, key, current) // 收集依赖 (先不用纠结一些函数的细节,先理清楚它的基础架构)
const nextTarget = target[key];
const observableResult = RawProxy.get(nextTarget) // 【尝试判断是否已经建立过收集能力】
if (observableResult) { // 【已经存在收集能力】
return observableResult //
}
return createObservable(target, key, nextTarget) // 每次get的时候,才会继续下一层观察能力的创建
},
set(target, key, value,receiver) {
const newValue = createObservable(target, key, value) // 重新建立收集能力
target[key] = newValue; // 设置值
runReactionsFromTargetKey({ // 通知收集过的依赖进行更新! (先不用纠结一些函数的细节,先理清楚它的基础架构)
target,
key,
value: newValue,
oldValue,
receiver,
type: 'set',
})
}
});
ProxyRaw.set(proxy, target); // 【方便后续根据proxy 获取target ProxyRaw 是一个WeakMap】
// RawProxy.set(target, proxy) //
if (shallow) { // 本次新增 ✅
RawShallowProxy.set(target, proxy) // 本次新增 ✅
} else {
RawProxy.set(target, proxy) // 【方便后续根据 target 获取proxy。 RawProxy 是一个WeakMap】
}
return proxy
}
正常的object类型大致就是这样了!但是如果是 Map、Set、WeakMap、WeakSet类型呢?
他们不会直接读取属性,而是通过 obj.get到方式去获取到,添加也是obj.add 去处理的,答案就是重写get和add等方法!
我们知道get和add方法都是读的操作!,所以我们proxy只需要读!
const instrumentations = {
get(key: PropertyKey) {
const target = ProxyRaw.get(this)
const proto = Reflect.getPrototypeOf(this) as any
const current = getNowReaction(); // 获取当前reaction
addRawReactionsMap(target, key, current) // 收集依赖 (先不用纠结一些函数的细节,先理清楚它的基础架构)
return findObservable(target, key, proto.get.apply(target, arguments))
},
add(key: PropertyKey) {
const target = ProxyRaw.get(this)
const proto = Reflect.getPrototypeOf(this) as any
const hadKey = proto.has.call(target, key)
// forward the operation before queueing reactions
const result = proto.add.apply(target, arguments)
if (!hadKey) {
runReactionsFromTargetKey({ target, key, value: key, type: 'add' })
}
return result
},
}
function findObservable(target: any, key: PropertyKey, value: any) {
const observableObj = RawProxy.get(value)
if (observableObj) {
return observableObj // 已经监听的情况下,直接放回!
}
if (!isObservable(value) && isSupportObservable(value)) {
return createObservable(target, key, value)
}
return value
}
const createCollectionProxy = (target: any, shallow?: boolean) => {
const proxy = new Proxy(target, {
get() {
// instrument methods and property accessors to be reactive
target = hasOwnProperty.call(instrumentations, key)
? instrumentations // get or add 等方法,我们使用工具等方式
: target
return Reflect.get(target, key, receiver)
}
})
return proxy
}
我们写一个统一版本出来!
export const createObservable = (
target: any,
key?: PropertyKey,
value?: any,
shallow?: boolean
) => {
if (typeof value !== 'object') return value // 必须是obejct,否则直接结束
if (target) {
const parentRaw = ProxyRaw.get(target) || target // target 存在的先拿出来
const isShallowParent = RawShallowProxy.get(parentRaw);
if (isShallowParent) return value // shallowProxy 就是不要做任何操作? 为啥? 后面再看
}
buildDataTree(target, key, value) //
if (isNormalType(value)) return createNormalProxy(value) // 创建
if (isCollectionType(value)) return createCollectionProxy(value)
// never reach
return value
}
const createNormalProxy = (target: any, shallow?: boolean) => {
const proxy = new Proxy(target, baseHandlers)
ProxyRaw.set(proxy, target); // 方便后续根据proxy 获取target
if (shallow) {
RawShallowProxy.set(target, proxy)
} else {
RawProxy.set(target, proxy) // 方便后续根据 target 获取proxy
}
return proxy
}
const createCollectionProxy = (target: any, shallow?: boolean) => {
const proxy = new Proxy(target, collectionHandlers)
ProxyRaw.set(proxy, target)
if (shallow) {
RawShallowProxy.set(target, proxy)
} else {
RawProxy.set(target, proxy)
}
return proxy
}
export const collectionHandlers = {
get(target: any, key: PropertyKey, receiver: any) {
// instrument methods and property accessors to be reactive
target = hasOwnProperty.call(instrumentations, key)
? instrumentations
: target
return Reflect.get(target, key, receiver)
},
}
const instrumentations = {
get(key: PropertyKey) {
const target = ProxyRaw.get(this)
const proto = Reflect.getPrototypeOf(this) as any
const current = getNowReaction(); // 获取当前reaction
addRawReactionsMap(target, key, current) // 收集依赖 (先不用纠结一些函数的细节,先理清楚它的基础架构)
return findObservable(target, key, proto.get.apply(target, arguments))
},
add(key: PropertyKey) {
const target = ProxyRaw.get(this)
const proto = Reflect.getPrototypeOf(this) as any
const hadKey = proto.has.call(target, key)
// forward the operation before queueing reactions
const result = proto.add.apply(target, arguments)
if (!hadKey) {
runReactionsFromTargetKey({ target, key, value: key, type: 'add' })
}
return result
},
}
const baseHandlers: ProxyHandler<any> = {
get(target, key, receiver) {
if (!key) return
const result = target[key] // use Reflect.get is too slow
if (typeof key === 'symbol' && wellKnownSymbols.has(key)) {
return result
}
// receiver
const current = getNowReaction(); // 获取当前reaction
addRawReactionsMap(target, key, current) // 收集依赖 (先不用纠结一些函数的细节,先理清楚它的基础架构)
const observableResult = RawProxy.get(result)
if (observableResult) {
return observableResult
}
if (!isObservable(result) && isSupportObservable(result)) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, key)
if (
!descriptor ||
!(descriptor.writable === false && descriptor.configurable === false)
) {
return createObservable(target, key, result)
}
}
return result
},
set(target, key, value, receiver) {
const hadKey = hasOwnProperty.call(target, key)
const newValue = createObservable(target, key, value)
const oldValue = target[key]
target[key] = newValue // use Reflect.set is too slow
if (!hadKey) {
runReactionsFromTargetKey({
target,
key,
value: newValue,
oldValue,
receiver,
type: 'add',
})
} else if (value !== oldValue) {
runReactionsFromTargetKey({
target,
key,
value: newValue,
oldValue,
receiver,
type: 'set',
})
}
return true
},
deleteProperty(target, key) {
const oldValue = target[key]
delete target[key]
runReactionsFromTargetKey({
target,
key,
oldValue,
type: 'delete',
})
return true
},
}
到了这里,基础架构就基本讲完了!
我们来看看一些细节是这么实现的!
::: 收集依赖 addRawReactionsMap
通知更新 runReactionsFromTargetKey :::
还记得我们之前说的addRawReactionsMap是怎么样一个数据结构么?他是一个二维的WeakMap,让我们看看具体实现细节!
const addRawReactionsMap = (
target: any,
key: PropertyKey,
reaction: Reaction
) => {
const reactionsMap = RawReactionsMap.get(target) // 通过target 获取 Map = reactionsMap
if (reactionsMap) { // 第一层key 获取的Map
const reactions = reactionsMap.get(key) // 通过Map 获取 ArraySet
if (reactions) { // ArraySet 存储的都是 reaction
reactions.add(reaction) // 增加一个
} else {
reactionsMap.set(key, new ArraySet([reaction])) // 创建新的ArraySet
}
return reactionsMap
} else { // 创建新Map
const reactionsMap: ReactionsMap = new Map([
[key, new ArraySet([reaction])],
])
RawReactionsMap.set(target, reactionsMap)
return reactionsMap
}
}
addRawReactionsMap明显是存的操作,而runReactionsFromTargetKey 则是取和执行的操作!
export const runReactionsFromTargetKey = (operation: IOperation) => {
let { key, type, target, oldTarget } = operation
batchStart() // 这里可以不着急理解它!
if (type === 'clear') {
// 删除之前,也要通知运行一次!
oldTarget.forEach((_: any, key: PropertyKey) => {
runReactions(target, key)
})
} else {
runReactions(target, key) // 取和执行操作!
}
if (type === 'add' || type === 'delete' || type === 'clear') {
// 当 add key 不存在的时候,or
const newKey = Array.isArray(target) ? 'length' : ITERATION_KEY
runReactions(target, newKey) // 取和执行操作!
}
batchEnd() // 这里可以不着急理解它!
}
const runReactions = (target: any, key: PropertyKey) => {
const reactions = getReactionsFromTargetKey(target, key) // 在二维map中获取,key收集的所有reations
const prevUntrackCount = UntrackCount.value
UntrackCount.value = 0 //
for (let i = 0, len = reactions.length; i < len; i++) {
const reaction = reactions[i]
if (reaction._isComputed) {
reaction._scheduler(reaction) // 正常执行
} else if (isScopeBatching()) {
PendingScopeReactions.add(reaction) // 批量收集! scope 是一波流
} else if (isBatching()) { // 不会立刻执行
PendingReactions.add(reaction) // 批量收集!,是一次性运行
} else {
if (isFn(reaction._scheduler)) {
reaction._scheduler(reaction) // 正常执行
} else {
reaction() // 也可以运行函数本身
}
}
}
UntrackCount.value = prevUntrackCount
}
。
const getReactionsFromTargetKey = (target: any, key: PropertyKey) => {
const reactionsMap = RawReactionsMap.get(target)
const reactions = []
if (reactionsMap) {
const map = reactionsMap.get(key)
if (map) {
map.forEach((reaction) => {
if (reactions.indexOf(reaction) === -1) {
reactions.push(reaction)
}
})
}
}
return reactions
}
2.2.2、reaction 的实现原理
[向右]2.2.2.1、autorun
autorun是一个函数类型收集的方案,典型的用法:
export function autorunDemo() {
const obj = observable({ name: '' });
const dispose = autorun(() => {
console.log(obj.name);
}); // 返回的dispose用于卸载订阅
obj.name = 'leimao'; // console一次
obj.name = 'yaoran'; // console再一次
dispose(); // 卸载监听
obj.name = 'haha'; // 不会再打印了
}
很显然autorun 本质是创建了一个reaction,并且会首次创建就执行(开始被访问的属性的Collection收集)!后续的这些属性修改就会让autorun自动被执行!
简单一些,我们写一个简易版!
const autorun = (tracker) => {
function reaction () {
if(!isFunction(tracker)) { // 非函数
return;
}
try {
ReactionStack.push(reaction); // 推入最前栈
callback(); // 执行函数,且开始搜集 最前栈的autorun 的reaction
} finally {
ReactionStack.pop();
}
}
reaction();
return () => {
// ... 卸载逻辑,稍后再讲
}
}
autorun 还要达到一个条件,就是 autorun的参数内部,对于属性的修改是按批执行的!
export function autorunIsBatch() {
const obj = observable({ name: '1' });
autorun(() => {
console.log(obj.name, '我是第一个autorun'); // 初始化执行一次 1 , 下面的会执行一次 6
});
autorun(() => {
console.log(obj.name, '我是第二个autorun'); // 初始化执行一次1, 后续不会执行
obj.name = '2';
obj.name = '3';
obj.name = '4';
obj.name = '5';
obj.name = '6';
});
}
打印结果如下:
::: 两个问题需要思考:
1、为什么?obj.name 做了那么多设值,结果第一个autorun只执行了一次!
原因是autorun的设计是按批执行的!也就是函数内部会统一一次处理!
2、第二个autorun,obj.name 收集了 它的reaction,紧接着修改了obj.name,理论上它应该会又出发了callback的执行, callback执行又会重新导致obj.name 被修改,从而死循环?
设置 _boundary 避免死循环! :::
batch应该怎么实现?
思路:
::: 1、设置batch计数!每次函数执行前 batch ++
2、然后函数内部的所有设置,都要判断 batch 是否> 0, 大于0咱们要把需要执行的reaction 存储起来;
3、函数执行后,一次遍历执行完成! :::
BatchCount.value++
tracker(); // 触发的runReactions要做判断!
BatchCount.value--
PendingReactions.batchDelete((reaction) => {
if (isFn(reaction._scheduler)) {
reaction._scheduler(reaction)
} else {
reaction()
}
})
const runReactions = (target: any, key: PropertyKey) => {
const reactions = getReactionsFromTargetKey(target, key)
for (let i = 0, len = reactions.length; i < len; i++) {
const reaction = reactions[i]
if (。。。) {
。。。
} else if (isBatching()) {
PendingReactions.add(reaction) // 批量收集!,是一次性运行
} else {
// never reach
if (isFn(reaction._scheduler)) {
reaction._scheduler(reaction)
} else {
reaction()
}
}
}
}
为了方便,我们把每一步设计成统一函数:
const BatchCount = { value: 0 }
const isBatching = () => BatchCount.value > 0
const batchStart = () => {
BatchCount.value++
}
const batchEnd = () => {
BatchCount.value--
if (BatchCount.value === 0) {
executePendingReactions()
}
}
autorun 进一步改造:
const autorun = (tracker) => {
function reaction () {
if(!isFunction(tracker)) { // 非函数
return;
}
try {
batchStart()
ReactionStack.push(reaction)
tracker()
} finally {
ReactionStack.pop()
batchEnd()
}
}
reaction();
return () => {
// ... 卸载逻辑,稍后再讲
}
}
死循环怎么办?是通过_boundary避免的!
const autorun = (tracker) => {
function reaction () {
if(!isFunction(tracker)) { // 非函数
return;
}
if (reaction._boundary > 0) return // 这样能够防止死循环!
try {
batchStart()
ReactionStack.push(reaction)
tracker()
} finally {
ReactionStack.pop()
reaction._boundary++ // 执行前增加 _boundary
batchEnd() // 最终批次会执行tracker
reaction._boundary = 0
}
}
reaction();
return () => {
// ... 卸载逻辑,稍后再讲
}
}
为了安全考虑再增加一个判断来加固!
const autorun = (tracker) => {
function reaction () {
if(!isFunction(tracker)) { // 非函数
return;
}
if (reaction._boundary > 0) return // 这样能够防止死循环!
if (ReactionStack.indexOf(reaction) === -1) {
try {
batchStart()
ReactionStack.push(reaction)
tracker()
} finally {
ReactionStack.pop()
reaction._boundary++ // 执行前增加 _boundary
batchEnd() // 最终批次会执行tracker
reaction._boundary = 0
}
}
}
reaction();
return () => {
// ... 卸载逻辑,稍后再讲
}
}
现在讲一讲卸载逻辑:其核心是找到谁搜集了当前reaction!然后通知这些搜集当前reaction的Arrayset,去除当前reaction!
const dispose = autorun(() => {
console.log(obj.name);
}); // 返回的dispose用于卸载订阅
dispose(); // 卸载监听
也就是dispose的逻辑!
我们上面说了核心是找到哪些搜集了当前reaction的Arrayset!那么怎么找到呢?
答案是前面我们搜集的时候,就要做好互相记录的准备!重新改写一下搜集逻辑才行!
改写之前 请不记得addRawReactionsMap(target, key, currentReaction)逻辑的,回到前面章节再熟悉一下这块逻辑!
addRawReactionsMap最终回返回 reactionsMap,这里面存放的是 target 下面所有key的 Arrayset,也就是
我们对它的返回记录到reaction里面去!
const reaction = nowReaction;
const reactionsMap = addRawReactionsMap(target, key, currentReaction);
const bindSet = reaction._reactionsSet // 核心点 _reactionsSet 反向存放搜集了当前reaction 的 reactionsMap
if (bindSet) {
bindSet.add(reactionsMap) // 添加!
} else {
reaction._reactionsSet = new ArraySet([reactionsMap]) // 新建
}
return bindSet
改造的好看一些:
const addReactionsMapToReaction = (
reaction: Reaction,
reactionsMap: ReactionsMap
) => {
const bindSet = reaction._reactionsSet
if (bindSet) {
bindSet.add(reactionsMap)
} else {
reaction._reactionsSet = new ArraySet([reactionsMap])
}
return bindSet
}
// 最终proxy的get地方:
new Proxy(obj, {
get() {
...
addReactionsMapToReaction(current, addRawReactionsMap(target, key, current))
...
}
})
反向记录了搜集了当前reaciton的reactionMaps了,需要删除的时候拿出来删了!
const dispose = () => {
reaction._reactionsSet?.forEach((reactionsMap) => {
reactionsMap.forEach((reactions) => {
// 不用知道当前key,全部执行一遍,保证删除干净!
reactions.delete(reaction)
})
});
delete reaction._reactionsSet;
}
autorun 核心逻辑到此为止!实际还有 autorun.effect 和 autorun.memo 的逻辑,这里不介绍了!
[向右] 2.2.2.2、computed
computed 是比较特殊的一个reaction种类!因为它除了自身是一个reaction,同时它还具备搜集能力!
const obj = observable({
name: 'dingeli',
age: 25,
});
const pinxie = observable.computed(() => {
console.log('重新计算了!'); // 执行一次
return `${obj?.name}:${obj.age}`;
});
autorun(() => {
console.log(pinxie.value, '真实值'); // 形成了收集链
});
obj.name = 'yaoran'; // 会打印重新计算了! computed的 schedule 1、标记dirty 2、通知自己搜集的重新运行;
autorun 被computed收集了起来!
computed被 obj.name + obj.age 都收集起来了!
::: obj.name 发生了更改
pinxie 设置了dirty
通知所有搜集的reaction更新
autorun 更新
重新计算 pingxie.value :::
export const computed: IComputed = (value) => {
const store: IValue = {};
const proxy = {};
const context = target ? target : store;
const property = target ? key : 'value';
const descriptor = getPrototypeDescriptor(target, property, value);
function compute() {
store.value = descriptor.get?.call(context);
}
function reaction() {
if (ReactionStack.indexOf(reaction) === -1) {
releaseBindingReactions(reaction);
try {
ReactionStack.push(reaction); // 推入栈种,准备让别人收集我!
compute(); // 开始计算,别人要收集我了!
} finally {
ReactionStack.pop();
}
}
}
reaction._name = 'ComputedReaction';
reaction._scheduler = () => {
// 别人通知我刷新,我直接转播
reaction._dirty = true; // 设置脏值
runReactionsFromTargetKey({
// 直接转播
target: context,
key: property,
value: store.value,
type: 'set',
});
};
reaction._isComputed = true;
reaction._dirty = true;
reaction._context = context;
reaction._property = property;
function get() {
if (hasRunningReaction()) {
// 收集别人之前,也让别人记录一下,我收集了它!
bindComputedReactions(reaction); // _computesSet.push(nowCurrentReaction)
}
if (!isUntracking()) {
//如果允许untracked过程中收集依赖,那么永远不会存在绑定,因为_dirty已经设置为false
// 第一次都是走的这个逻辑!
if (reaction._dirty) {
// 脏了,才会重新计算,也就是有缓存
reaction(); //
reaction._dirty = false;
}
} else {
// 不做收集!
compute();
}
// 收集起来!收集别人!
bindTargetKeyWithCurrentReaction({
target: context,
key: property,
type: 'get',
});
return store.value;
}
function set(value: any) {
try {
batchStart();
descriptor.set?.call(context, value);
} finally {
batchEnd();
}
}
if (target) {
Object.defineProperty(target, key, {
get,
set,
enumerable: true,
});
return target;
} else {
Object.defineProperty(proxy, 'value', {
set,
get,
});
buildDataTree(target, key, store);
proxy[ObModelSymbol] = store; // store
}
return proxy;
};
[向右]2.2.2.3、observe
后续会单独开个章节来讲他的逻辑比较独立!
结束语
reactive是整个formily最底层的逻辑,核心点是通过proxy的特殊拦截能力, 使得对象的每个key都有一个独立的ArraySet去订阅reaction;
proxy的 set,则是发布订阅的发布步骤;
至此完成了自闭环!
转载自:https://juejin.cn/post/7411043646131028020