实现vue3源码-reactive & readonly嵌套对象转换

122 阅读2分钟

在我们使用reactive & readonly时会发现,如果传入的是深层嵌套数据结构也能够进行响应式包裹或只读约束,之前实现的呢只是浅层的,今天来完善一下这两个深层嵌套的问题,代码也非常简单,实现方式如出一辙

reactive

readonly

结语

reactive

测试

首先就是看看深层是不是响应式

it('nested reavtive', () => {
        const original = {
            nested: {
                foo: 1
            },
            array: [{ bar: 2 }]
        }

        const observed = reactive(original)

        expect(isReactive(observed.nested)).toBe(true)
        expect(isReactive(observed.array)).toBe(true)
        expect(isReactive(observed.array[0])).toBe(true)
    })

实现

实现我们只需要在代理get的时候进行判断,看看是否需要再进行reactive(XXX)调用

这里判断是不是对象以后用的会非常多所以抽离出去到 shared 就行

function createGetter(isReadonly = false) {
    return function get(target, key) {
        const res = Reflect.get(target, key)

        // 判断是不是 reactive
        if (key === ReactiveFlags.IS_REACTIVE) {
            return !isReadonly
        } else if (key === ReactiveFlags.IS_READONLY) {
            return isReadonly
        }

        // 判断是不是 对象
        // shared: export const isObject = (value) => value !== null && typeof value === 'object'
        if (isObject(res)) {
            return reactive(res)
        }

        // 依赖收集
        // 抽离公共,判断是否需要收集依赖<readonly>
        !isReadonly && track(target, key)
        return res
    }
}

readonly

测试

首先就是看看深层是不是约束为只读属性了

it('should make nested values readonly', () => {

        // readonly 不可以被set<改写>

        const original = { foo: 1, bar: { baz: 2 } }
        const wrapped = readonly(original)

        expect(wrapped).not.toBe(original)
        expect(isReadonly(wrapped)).toBe(true)
        expect(isReadonly(original)).toBe(false)
        expect(isReadonly(wrapped.bar)).toBe(true)
        expect(isReadonly(original.bar)).toBe(false)
        expect(wrapped.foo).toBe(1)
    })

实现

实现和reactive一样,在代理get的时候进行判断,看看是否需要再进行readonly(XXX)调用

这里和reactive判断机制一样的所以用三元判断进行调用会更好,之前用的 isReadonly 现在也派上用场了

function createGetter(isReadonly = false) {
    return function get(target, key) {
        const res = Reflect.get(target, key)

        // 判断是不是 reactive
        if (key === ReactiveFlags.IS_REACTIVE) {
            return !isReadonly
        } else if (key === ReactiveFlags.IS_READONLY) {
            return isReadonly
        }

        // 判断是不是 对象
        // shared: export const isObject = (value) => value !== null && typeof value === 'object'
        if (isObject(res)) {
            return isReadonly
                ? readonly(res)
                : reactive(res)
        }

        // 依赖收集
        // 抽离公共,判断是否需要收集依赖<readonly>
        !isReadonly && track(target, key)
        return res
    }
}

结语

源码其实都是一步一步的看,一点一点的写,不断地完善,不断重构出来的。技术是积累出来的,经验都是实践得来,希望我往后的文章能够给大家带来更多的收益,即使收效甚微,感谢!