实现vue3源码-ref

88 阅读3分钟

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的其他功能了