vuevue3
Thursday, February 23, 2023
javascript
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
javascript
// mutableHandlers就是Proxy的配置
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
javascript
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
...
const proxy = new Proxy(
target,
// 这个就是上面的mutableHandlers
baseHandlers
)
...
return proxy
}
javascript
JavaScript
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
...
const res = Reflect.get(target, key, receiver)
...
if (!isReadonly) {
// 收集依赖核心就是这个
track(target, TrackOpTypes.GET, key)
}
...
return res
}
}
javascript
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
trackEffects(dep)
}
}
export function trackEffects(
dep: Dep, // dep = new Set()
) {
let shouldTrack = false
shouldTrack = !dep.has(activeEffect!)
if (shouldTrack) {
dep.add(activeEffect!)
// 双向收集 利于清理 后面会讲
activeEffect!.deps.push(dep)
}
}
javascript
let product = reactive({price: 5, quantity: 2})
// targetMap里存储的就是这样一个结构
targetMap = new WeakMap([[{price: 5, quantity: 2}, new Map({
['price', new Set()]
['quantity', new Set()]
})]])
javascript
let product = reactive({price: 5, quantity: 2})
effect(() => {
product.price * product.quantity
})
javascript
let product = reactive({price: 5, quantity: 2})
product.price
product['price']
Reflect.get(product, 'price')
javascript
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 重点是这个ReactiveEffect
const _effect = new ReactiveEffect(fn)
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
if (!options || !options.lazy) {
_effect.run()
}
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
javascript
export class ReactiveEffect<T = any> {
active = true
// 用来记录当前的activeEffect被哪些属性收集了, 主要是用来清理看按下面的cleanupEffect函数
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
// parent用来处理嵌套 effect
// 例如:
// effect(() => { -> effect 1
// state.name
// effect(() => { -> effect 2
// state.age
// })
// state.address
// })
//
// 第一次取state上的name的时候,能收集到effect 1,然后取内层的state的age的时候,
// age会收集到effect 2,然后内层effect的函数执行完后,
// 我们就把activeEffect置为undefined了,那么最后取state上的 address的时候,
// 它的get里拿到的activeEffect就是undefined了
//
// 利用parent解决
// 我们在effect上都增加一个属性parent,最外层的effect上的parent就是undefined
// 当我们执行下一个effect的时候,它的parent就是effect 1,age收集的就是effect 2
// 当我们取state上的address的时候,再把this.parent赋值给activeEffect就可以了
// 简而言之就是,当前自己的effect执行完了,就把自己的父节点赋值给activeEffect就解决了
// effect 2 会将 effect 1 作为自己的 parent
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
this.parent = activeEffect
activeEffect = this
shouldTrack = true
// 每次执行之前先清理
// 举个例子
// effect(() = {
// flag ? state.name : state.age
// })
// 当这个effect执行的时候, flag可能会进行变化,当它变化的时候我们就需要收集另一个属性,而释放掉当前属性收集的effect
//
cleanupEffect(this)
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
// 再还原回来
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
if (this.deferStop) {
this.stop()
}
}
}
stop() {
// stopped while running itself - defer the cleanup
if (activeEffect === this) {
this.deferStop = true
} else if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
function cleanupEffect(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
javascript
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
...
const result = Reflect.set(target, key, value, receiver)
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
javascript
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
if (key !== void 0) {
deps.push(depsMap.get(key))
}
if (deps.length === 1) {
if (deps[0]) {
triggerEffects(deps[0])
}
} else {
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
triggerEffects(createDep(effects))
}
}
export function triggerEffects(
dep: Dep | ReactiveEffect[],
) {
const effects = isArray(dep) ? dep : [...dep]
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect)
}
}
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect)
}
}
}
function triggerEffect(
effect: ReactiveEffect,
) {
// 全局变量activeEffect保存着当前正在执行的effect
// 当前正在执行的effect不是自己的时候,我们在执行自己 避免死循环
if (effect !== activeEffect) {
effect.run()
}
}