美文网首页
[学习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