Skip to content

深度代理

vue
<template>
    <div ref="wrapper"></div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { effect, reactive } from '../source/reactivity'

const wrapper = ref(null)

const state = reactive({
    name: '小李大人',
    age: 18,
    address: {
        city: '杭州',
        province: '浙江'
    }
})

onMounted(() => {
    effect(() => {
        wrapper.value.innerHTML = `姓名:${state.name},年龄:${state.age},城市:${state.address.city},省份:${state.address.province}`
    })

    setTimeout(() => {
        state.address.city = '宁波'
    }, 1000)
})

</script>

页面效果 页面效果

1s后:

页面效果

可以看到,state.address.city 的值发生了变化,但是页面并没有重新渲染, 这是因为 state.address 并没有被代理,所以它的属性并没有被依赖收集。

js
import { isObject } from '../utils'
import { track, trigger } from './reactiveEffect'

const IS_REACTIVE_KEY = '__v_isReactive'

/** 缓存代理过的对象 */
const reactiveMap = new WeakMap()

const mutableHandlers = {
    get(target, key, receiver) {

        // 访问对象是否被代理过的属性
        if (key === IS_REACTIVE_KEY) return true

        const value = Reflect.get(target, key, receiver)

        // 依赖收集
        track(target, key)

        if (isObject(value)) {
            return reactive(value)
        }

        return value
    },
    set(target, key, value, receiver) {

        const oldValue = target[key]

        const result = Reflect.set(target, key, value, receiver)

        if (oldValue !== value) {
            // 触发更新
            trigger(target, key, value, oldValue)
        }

        return result
    }
}

/** 实现一个reactive函数,用于创建响应式对象 */
export function reactive(obj) {
    // 首先判断参数是不是对象
    if (!isObject(obj)) {
        return obj
    }

    // 判断对象是否已经被代理过
    if (obj[IS_REACTIVE_KEY]) {
        return obj
    }

    // 如果代理过,则直接返回代理过的对象
    if (reactiveMap.has(obj)) {
        return reactiveMap.get(obj)
    }

    const proxy = new Proxy(obj, mutableHandlers)

    // 缓存代理过的对象
    reactiveMap.set(obj, proxy)

    return proxy
}

reactive 函数中,我们在get中判断获取的值是不是一个对象,如果是一个对象那么就递归的调用reactive函数,将其变成响应式对象。

修复完成后的效果如下:

页面效果 页面效果

1s后:

页面效果