美文网首页
[学习vue3]响应式原理[二]

[学习vue3]响应式原理[二]

作者: 巧克力_404 | 来源:发表于2022-02-13 00:41 被阅读0次

    响应式原理V2 vs V3

    vue2的方式使用Object.defineProperty(),拦截每一个key,需要递归遍历对象所有key,速度慢,数组响应式需要额外实现'push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort',新增或删除属性无法监听,需要使用特殊api,不支持Map、Set、Class等数据结构。

    vue3的方式使用Proxy代理整个对象,从而侦查数据变化。

    造个轮子

    首先实现reactive(obj),借助Proxy代理传入的obj,这样可以拦截对obj的各种访问;

    function reactive(obj) {
        if (typeof obj !== 'object' && obj != null) {
            return obj
        }
        // Proxy相当于在对象外层加拦截
        // http://es6.ruanyifeng.com/#docs/proxy const observed = new Proxy(obj, baseHandler) return observed
    }
    

    下面是依赖收集的实现,原理如下图:


    相关api有

    • effect(fn):传入fn,返回的函数将是响应式的,内部代理的数据发生变化,它会再次执行
    • track(target, key):建立响应式函数与其访问的目标(target)和键(key)之间的映射关系
    • trigger(target, key):根据track()建立的映射关系,找到对应响应式函数并执行它

    基本结构:

    // 临时存储响应式函数 const effectStack = []
    // 将传入fn转换为一个响应式函数
    function effect(fn, options = {}) { }
    // 存放响应式函数和目标、键之间的映射关系 const targetMap = new WeakMap()
    // 依赖收集,创建映射关系
    function track(target, key) { }
    // 根据映射关系获取响应函数
    function trigger(target, key) { }
    

    实现effect()/track()/trigger()

    function effect(fn, options = {}) {
        // 创建reactiveEffect
        const e = createReactiveEffect(fn, options)
        // 执行一次触发依赖收集 
        e()
        return e
    }
    function createReactiveEffect(fn, options) {
        // 封装一个高阶函数,除了执行fn,还要将自己放入effectStack为依赖收集做准备 const effect = function reactiveEffect(...args) {
        if (!effectStack.includes(effect)) {
            try {
                // 1.effect入栈 effectStack.push(effect) // 2.执行fn
                return fn(...args)
            } finally {
                // 3.effect出栈 effectStack.pop()
            }
        }
    
        return effect
    }
    function track(target, key) {
        // 获取响应式函数
        const effect = effectStack[effectStack.length - 1]
        if (effect) {
            // 获取target映射关系map,不存在则创建 
            let depMap = targetMap.get(target)
            if (!depMap) {
                depMap = new Map()
                targetMap.set(target, depMap)
            }
            // 获取key对应依赖集合,不存在则创建 
            let deps = depMap.get(key)
            if (!deps) {
                deps = new Set()
                depMap.set(key, deps)
            }
            // 将响应函数添加到依赖集合
    
            deps.add(effect)
        }
    }
    function trigger(target, key) {
        // 获取target对应依赖map
        const depMap = targetMap.get(target)
        if (!depMap) {
            return
        }
        // 获取key对应集合
        const deps = depMap.get(key)
        if (deps) {
            // 执行所有响应函数
            deps.forEach(dep => dep())
        }
    }
    // 测试
    const state = reactive({ foo: 'foo' })
    effect(() => {
        console.log('effect', state.foo);
    })
    

    结合视图验证一下

    <script src="03-reactivity.js"></script>
    <script>
        const obj = { name: 'kkb', age: 8 }
        const data = reactive(obj)
        // effect()定义我们的更新函数 
        effect(() => {
            app.innerHTML = ` <h1>${data.name}今年${data.age}岁了</h1>`
        })
        // 修改一下数值 
        setInterval(() => {
            data.age++
        }, 1000);
    </script>
    

    计算属性也很常用,可以基于effect实现

    const double = computed(() => data.age * 2)
    // effect()定义我们的更新函数
    effect(() => {
        app.innerHTML = ` <h1>${data.name}今年${data.age}岁了</h1> <p>乘以2是${double.value}岁</p>`
    })
    

    computed(fn):可以使传入fn使之成为响应式函数,fn内部依赖的数值发生变化,该函数应该重新执行 获得最新的计算结果。

    相关文章

      网友评论

          本文标题:[学习vue3]响应式原理[二]

          本文链接:https://www.haomeiwen.com/subject/bmtflrtx.html