ref我相信大家也不陌生,开发时使用频率也不低了,ref的实现也不是很难,理清思路就好,下面就实现下ref
ref
收集依赖及触发依赖
引用数据类型
代码
结语
ref
测试
下面呢其实就是我们使用ref的场景以及期望值
describe('ref', () => {
it('happy path', () => {
const a: any = ref(1)
expect(a.value).toBe(1)
})
it('should make nested properties reactive', () => {
const a: any = 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)
// same value should not trigger
a.value = 2
expect(calls).toBe(2)
expect(dummy).toBe(2)
})
it('should make nested properties reactive', () => {
const a: any = ref({
count: 1
})
let dummy
effect(() => {
dummy = a.value.count
})
expect(dummy).toBe(1)
a.value.count = 2
expect(dummy).toBe(2)
})
})
实现
ref其实和reactive的实现思路类似,也都是更贴切面向对象实现
class RefImpl {
private _value
constructor(value) {
this._value = value
}
get value() {
return this._value
}
set value(newValue){
}
}
export function ref(value) {
return new RefImpl(value)
}
收集依赖及触发依赖
测试
it('it should be effect', () => {
let dummy
let obj = ref(1)
let count = 0
effect(() => {
count++
dummy = obj.value
})
expect(count).toBe(1)
expect(dummy).toBe(1)
obj.value = 2
expect(count).toBe(2)
expect(dummy).toBe(2)
})
ref的收集依赖及触发依赖和reactive的思路一样,同样是get -> track,set -> trigger
ref单层所以不需要reactive收集依赖的数据结构,只需要一层dep就够了,这里涉及到reactive & ref都使用的set层收集,所以抽离出来提高复用 'trackEffects',trigger 同理 'triggerEffects'
const targetMap = new Map()
export function 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 function trigger(target, key) {
let depsMap = targetMap.get(target)
let dep = depsMap.get(key)
triggerEffects(dep)
}
export function trackEffects(dep) {
if (dep.has(activeEffect)) return
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
export function triggerEffects(dep) {
for (const effect of dep) {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
这里还需要做一下是否需要收集依赖的判断,直接在入口 get 里处理就行
// ref.ts
get value() {
trackRefValue(this)
return this._value
}
export function trackRefValue(ref) {
if (isTracking()) {
trackEffects(ref.dep)
}
}
// effect.ts
export function isTracking() {
return shouldTrack && activeEffect !== undefined
}
引用数据类型
测试
it('should make nested properties reactive', () => {
const a: any = ref({
count: 1
})
let dummy
effect(() => {
dummy = a.value.count
})
expect(dummy).toBe(1)
a.value.count = 2
expect(dummy).toBe(2)
})
实现
class RefImpl {
private _value: any
public dep
private _rawValue: any
constructor(value: any) {
this._rawValue = value
// value 为对象时转为reactive包一层
this._value = convert(value)
this.dep = new Set()
}
get value() {
trackRefValue(this)
return this._value
}
set value(newValue) {
// 不展开说 Object.is,Object.is 理解为更为严格的三等号 === 比较就行
// shared: export const hasChanged = (oldValue: any, newValue: any) => Object.is(oldValue, newValue)
if (hasChanged(this._rawValue, newValue)) return
this._rawValue = newValue
this._value = convert(newValue)
triggerEffects(this.dep)
}
}
export function convert(value) {
return isObject(value) ? reactive(value) : value
}
这里使用_rawValue进行比较其实是因为reactive返回的是一个proxy,在构造函数开始时就直接保存原始值,后面对比时也是使用原始值,这样对比才符合ref包裹引用类型的情况
代码
ref.ts
import { hasChanged, isObject } from "../shared"
import { isTracking, trackEffects, triggerEffects } from "./effect"
import { reactive } from "./reactive"
class RefImpl {
private _value: any
public dep
private _rawValue: any
constructor(value: any) {
this._rawValue = value
// value 为对象时转为reactive包一层
this._value = convert(value)
this.dep = new Set()
}
get value() {
trackRefValue(this)
return this._value
}
set value(newValue) {
if (hasChanged(this._rawValue, newValue)) return
this._rawValue = newValue
this._value = convert(newValue)
triggerEffects(this.dep)
}
}
export function trackRefValue(ref) {
if (isTracking()) {
trackEffects(ref.dep)
}
}
export function convert(value) {
return isObject(value) ? reactive(value) : value
}
export function ref(value) {
return new RefImpl(value)
}
effect.ts
import { extend } from "../shared"
const targetMap = new Map()
export function 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 function trigger(target, key) {
let depsMap = targetMap.get(target)
let dep = depsMap.get(key)
triggerEffects(dep)
}
export function trackEffects(dep) {
if (dep.has(activeEffect)) return
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
export function triggerEffects(dep) {
for (const effect of dep) {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
export function isTracking() {
return shouldTrack && activeEffect !== undefined
}
shared.ts
export const isObject = (value: any) => value !== null && typeof value === 'object'
// 不展开说 Object.is,Object.is 理解为更为严格的三等号 === 比较就行
export const hasChanged = (oldValue: any, newValue: any) => Object.is(oldValue, newValue)
结语
这里ref的基本结构也实现了,下一章就可以实现ref的其他功能了