实现 ref 函数
ref
其实和reactive
一样,都是创建响应式数据的方法。只不过 ref 是一个单值,只能用 .value 去获取或者修改。
单测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import { effect } from '../effect' import { reactive } from '../reactive' import { ref, isRef, unRef, proxyRefs } from '../ref' describe('ref', () => { it('happy path', () => { const a = ref(1) expect(a.value).toBe(1) }) it('should be reactive', () => { const a = ref(1) let dummy let calls = 0 effect(() => { calls++ dummy = a.value }) expect(calls).toBe(1) expect(dummy).toBe(1) a.value = 2 expect(calls).toBe(2) expect(dummy).toBe(2) a.value = 2 expect(calls).toBe(2) expect(dummy).toBe(2) }) it('should make nested properties reactive', () => { const a = ref({ count: 1 }) let dummy effect(() => { dummy = a.value.count }) expect(dummy).toBe(1) a.value.count = 2 expect(dummy).toBe(2) }) })
|
实现单测
1 2
| export const hasChanged = (newVal, val) => !Object.is(val, newVal)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { trackEffects, triggerEffects, isTracking } from './effect' import { reactive } from './reactive' import { hasChanged, isObject } from '../shared'
const convert = val => (isObject(val) ? reactive(val) : val)
class RefImpl { private _value: any public dep private _rawValue: any constructor(value) { this._rawValue = value this._value = convert(value)
this.dep = new Set() } get value() { tarckRefValue(this) return this._value } set value(newVal) { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = convert(newVal) triggerEffects(this.dep) } } }
const tarckRefValue = ref => { if (isTracking()) trackEffects(ref.dep) }
export const ref = value => new RefImpl(value)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
export const isTracking = () => shouldTrack && activeEffect !== undefined
export const trackEffects = dep => { if (dep.has(activeEffect)) return dep.add(activeEffect) activeEffect.deps.push(dep) }
export const track = (target, key) => { if (!isTracking()) return let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } let dep = depsMap.get(key) if (!dep) { dep = new Set() depsMap.set(key, dep) } trackEffects(dep) } export const triggerEffects = dep => { for (const effect of dep) { if (effect.scheduler) effect.scheduler() else effect.run() } }
export const trigger = (target, key) => { let depsMap = targetMap.get(target) let dep = depsMap.get(key) triggerEffects(dep) }
|