实现 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
//reactivity/__test__/ref.spec.ts
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
//shared/index.ts
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
//reactivity/ref.ts
import { trackEffects, triggerEffects, isTracking } from './effect'
import { reactive } from './reactive'
import { hasChanged, isObject } from '../shared'
/**对象转换为reactive对象 */
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
//reactivity/effect.ts
/**是否收集依赖 */
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
// target -> key -> dep
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)
}