美文网首页
vue3如何做依赖收集?

vue3如何做依赖收集?

作者: myth小艾 | 来源:发表于2021-12-15 12:04 被阅读0次

整体结论

  1. reactive做数据代理和负责依赖收集的触发

  2. effect 做数据副作用的触发函数,函数内部自运行effect()触发reactive的get

  3. track get的触发做effectFn的快照收集,结构用weakMap保存(下面详细介绍),依赖收集已经做好

  4. trigger 当原始值删除或者改变触发reactive中set和deleteProperty执行trigger,他把weakMap中的effectFn找出来然后重新执行一遍

tips:建议与下面简版源码一起食用

weakMap中的数据结构

单独拧出来说,搞懂这个基本搞懂50%~

const obj = reactive({ a: 'a', b: { c: 1 } })//测试数据,生成的weakMap如下
image.png

三层结构:weakMap ==>Map ==>Set

  • weakMap:存储对象和key的关系

    • key:target对象,对应的是effect中引用的层级深度,也就是说{ a: 'a', b: { c: 1 } }一个 { c: 1 }两个 为什么是这样设计的呢?因为proxy的回调target就是原始对象,也在做深度响应的时候,通过对象去找也只会触发一次trigger()
    • vaue:Map
  • Map:存储key和effectFn的关系

    • key:对应的每个层级的key a/b c
    • value:Set
  • Set:存储对应的effect函数

    • value:effect传进来的函数做存储

下面直接贴代码~

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="aDom"></div>
    <div id="bDom"></div>
    <script>
        //数据响应式
        function reactive(obj) {
            if (!isObject(obj)) {
                return obj
            }
            return new Proxy(obj, {
                get(target, key) {
                    //Reflect容错率不错,错误会正确的处理
                    const res = Reflect.get(target, key)
                    console.log('get', res)
                    track(target, key)
                    return isObject(res) ? reactive(res) : res
                },
                set(target, key, value) {
                    const res = Reflect.set(target, key, value)
                    console.log('set', key)
                    trigger(target, key)
                    return res
                },
                deleteProperty(target, key) {
                    const res = Reflect.deleteProperty(target, key)
                    console.log('del', key)
                    trigger(target, key)
                    return res
                }
            })
        }
        function isObject(obj) {
            return typeof obj === 'object'
        }
        // 保存函数
        const effectStack = []
        //保存映射关系的数据结构
        const targetMap = new WeakMap()
        //触发依赖收集
        function effect(fn) {
            //对照源码简化的高阶函数,可以从简,但是为了原滋原味
            const e = createReactiveEffect(fn)
            //自运行
            e()
            return e
        }
        function createReactiveEffect(fn) {
            //错误处理
            //effectStack 进栈
            //执行完依赖收集后出栈保证effectStack最后值的获取
            const effectFn = function () {
                try {
                    effectStack.push(effectFn)
                    fn()
                } finally {
                    effectStack.pop()
                }
            }
            return effectFn
        }
        //跟踪函数:负责依赖收集
        function track(target, key) {
            //1. 获取effectFn
            const effect = effectStack[effectStack.length - 1]
            if (effect) {
                //用过target获取对应的map  每一层级的数据对应一个targetMap   target:Map
                let depMap = targetMap.get(target)
                //首次进入depMap为空,需要创建
                if (!depMap) {
                    depMap = new Map()
                    targetMap.set(target, depMap)
                }
                // 2. 通过key获取依赖集合set
                let deps = depMap.get(key)
                if (!deps) {
                    deps = new Set()
                    depMap.set(key, deps)
                }
                // 3. 放入effect
                deps.add(effect)
                //层级关系是 obj:{key:effect}  三层套娃   WeakMap ==> Map ==>Set
            }
        }
        //触发函数:track()相反操作,拿出映射关系,执行所有的cbs
        function trigger(target, key) {
            const depMap = targetMap.get(target)
            if (!depMap) return
            const deps = depMap.get(key)
            if (deps) {
                deps.forEach(dep => dep())
            }
        }
        const obj = reactive({ a: 'a', b: { c: 1 } })
        //源码中  这里做虚拟dom的path,这里简单的用textContent改变页面上的值做响应式
        effect(() => {
            aDom.textContent = obj.a
            console.log('effect1', obj.a)
        })
        //深层次的副作用
        effect(() => {
            bDom.textContent = obj.b.c
            console.log('effect2', obj.b.c)
        })
        setTimeout(() => {
            obj.a = '777'
            //当改变深度子节点 只会触发一次trigger 因为weakMap是以obj的target(递归中就是每一层的对象)存储的
            obj.b.c = "666"
        }, 2000)
    </script>
</body>
</html>

都看到这里了~三连我就不要了,点个赞就行~哈哈

相关文章

网友评论

      本文标题:vue3如何做依赖收集?

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