首页 > 其他 > 详细

创建一个简单的迷你Vue3-1

时间:2020-07-19 14:37:22      阅读:39      评论:0      收藏:0      [点我收藏+]

Reactivity响应式数据系统

经过前面第一章,我们已经建好了渲染系统和Vdom相关。

那第二步则是构建双向绑定的响应式数据系统,我们知道在Vue2中数据双向绑定是通过Object.defineProperty来实现的,通过这个API来实现的数据双向绑定有一些缺点。主要是:

i. 所有需要进行绑定观察的对象属性必须要先定义好,因为在实现上是通过遍历所有key来修改getter和setter方法来实现的,所以新增的属性就没办法响应式了,除非手动调用

方法来进行处理

ii. 数组类的操作因为不会触发getter和setter,因此Vue2是重写了Array的push,splice等方法来实现的响应式数据,因此直接修改数组指定index的item时候无法触发响应式的更新,

并且重写数组原生方法也可能带来一些意想不到的问题。

基于此,Vue3使用ES6新增的Proxy属性来实现响应式数据绑定,上述所提到的两个问题都解决了,唯一的缺点就是IE系列低版本并不支持这个特性。当然我们因为业务都是在移动端的,

因此并没有这个情况。这个情况应该是Vue3框架他们应当考虑的问题了。关于Proxy可以前往MDN参考Proxy特性

 

介绍完了基本的情况,下面我们就来开始实现它:

 思路:

要想实现一个双向数据绑定,并且在数据更新的时候能够通知到使用方,那么我们肯定得在数据读取和设置的两个地方都要有狗子,当我们读取数据的时候,将数据的依赖函数注册,当我们

设置数据的时候,将新的值作为参数传递给注册的函数并调用。这样就实现了响应式的数据系统。

 

实现:

一、建立依赖关系管理类Dep
<html>
    <head>
        <title>Mini Vue3</title>
    </head>
    <body>
        <script>
            let activeEffect = null;

            class Dep {
                // 构造函数
                constructor (value) {
                    // 初始化订阅列表
                    this.subscribers = new Set();
                    // 存储需要跟踪依赖的值
                    this._value = value;
                }

                get value() {
                    // 取值时添加依赖
                    this.depend();
                    return this._value;
                }

                set value(newValue) {
                    // 设置时通知更新
                    this._value = newValue;
                    this.notify();
                }

                // 添加依赖
                depend() {
                    if (activeEffect) {
                        this.subscribers.add(activeEffect);
                    }
                }

                // 依赖更新,通知回调
                notify() {
                    this.subscribers.forEach(effect => {
                        effect();
                    });
                }
            }

            /**
             * @ddecription 注册事件,当
             */
            function watchEffect(effect) {
                activeEffect = effect;
                effect();
                activeEffect = null;
            }

            const dep = new Dep(hello);

            // 注册依赖
            watchEffect(() => {
                console.log(dep.value);
            });
            // 依赖更新
            dep.value = changed;
        </script>
    </body>
</html>

 

这个依赖关系类起什么作用的呢,我们看到其中有存储注册回调的subscribes,添加依赖的depend,以及通知依赖更新的notify。

当我们在get value时注册依赖,将watchEffect中的effect回调保存下来。然后在set value的时候通知回调执行。这样我们就实现了一个自动管理依赖的class了。

那么这里的watchEffect是个什么函数呢,跟之前Vue2中的watch有什么区别呢?

可以看下目前Vue3的组合式API草案文档中关于watchEffect的描述:watchEffect

与之前watch不同的是,这个会立即执行,不需要immediate参数。并且可以在任意地方引入。

技术分享图片

 

 

可以看到已经实现了我们的目的,立即执行输出hello,并且在value改变之后触发了执行输出了changed。

 因此此时已经建立依赖关系类Dep,但是我们在实际Vue3中用到的则是对一个对象建立响应式观察用的是reactive方法,具体可以参考Reactive

因为这里的Dep我们应当是用来作为依赖管理类,而不是监控自身的值。对于对象值的处理应当由reactive方法处理,因此我们来实现reactive方法如下:

<html>
    <head>
        <title>Mini Vue3</title>
    </head>
    <body>
        <script>
            let activeEffect = null;

            class Dep {
                subscribers = new Set();

                // 添加依赖
                depend() {
                    if (activeEffect) {
                        this.subscribers.add(activeEffect);
                    }
                }

                // 依赖更新,通知回调
                notify() {
                    this.subscribers.forEach(effect => {
                        effect();
                    });
                }
            }

            /**
             * @decription 注册事件,当依赖的对象发生改变时,触发effect方法执行
             */
            function watchEffect(effect) {
                activeEffect = effect;
                effect();
                activeEffect = null;
            }

            /**
             * @description 给对象封装并返回响应式代理
             */
            function reactive(oldObj) {

            }

            // 需要实现的效果类似于
            const state = reactive({
                count: 0
            });

            watchEffect(() => {
                console.log(state.count);
            });
            // 第一次应当输出0

            state.count += 1;
            // 此时应当输出1
        </script>
    </body>
</html>

我们应当要实现这样的效果。接下来我们就要填充reactive方法

如果是在Vue2,那么方法类似这样:

            /**
             * @description 给对象封装并返回响应式代理
             */
            function reactive(oldObj) {
                Object.keys(oldObj).forEach(key => {
                    const dep = new Dep();
                    let value = oldObj[key];

                    Object.defineProperty(oldObj, key, {
                        get () {
                            // 注册依赖
                            dep.depend();
                            return value;
                        },
                        set (newValue) {
                            value = newValue;
                            // 通知依赖更新
                            dep.notify();
                        }
                    });

                    return oldObj;
                });
            }

这也是为什么在Vue2中新增的属性不会自动加入响应式,而需要通过Vue.set手工调用。

而在Vue3中使用的是Proxy,则没有这个问题了。所以在Vue3中的reactive方法实现如下:

 

<html>
    <head>
        <title>Mini Vue3</title>
    </head>
    <body>
        <script>
            let activeEffect = null;

            class Dep {
                subscribers = new Set();

                // 添加依赖
                depend() {
                    if (activeEffect) {
                        this.subscribers.add(activeEffect);
                    }
                }

                // 依赖更新,通知回调
                notify() {
                    this.subscribers.forEach(effect => {
                        effect();
                    });
                }
            }

            /**
             * @decription 注册事件,当依赖的对象发生改变时,触发effect方法执行
             */
            function watchEffect(effect) {
                activeEffect = effect;
                effect();
                activeEffect = null;
            }

            const reactiveHandlers = {
                get(target, key, receiver) {
                    
                },
                set(target, key, value, receiver) {

                }
            };

            /**
             * @description 给对象封装并返回响应式代理
             */
            function reactive(oldObj) {
                return new Proxy(oldObj, reactiveHandlers);
            }

            // 需要实现的效果类似于
            const state = reactive({
                count: 0
            });

            watchEffect(() => {
                console.log(state.count);
            });
            // 第一次应当输出0

            state.count += 1;
            // 此时应当输出1
        </script>
    </body>
</html>

但是这里有个问题,因为reactiveHandlers作为公共handler定义了,那么每个对象的依赖要如何管理呢?

我们通过一个weakMap来管理所有的依赖关系。之所以使用weakMap,因为weakMap只能使用对象做key,所以不能遍历.因此当对象被回收之后,map内的引用全无,因此map也能够被回收,这是Vue3这里之所以使用weakMap的原因。

新的代码如下:

<html>
    <head>
        <title>Mini Vue3</title>
    </head>
    <body>
        <script>
            let activeEffect = null;

            class Dep {
                subscribers = new Set();

                // 添加依赖
                depend() {
                    if (activeEffect) {
                        this.subscribers.add(activeEffect);
                    }
                }

                // 依赖更新,通知回调
                notify() {
                    this.subscribers.forEach(effect => {
                        effect();
                    });
                }
            }

            /**
             * @decription 注册事件,当依赖的对象发生改变时,触发effect方法执行
             */
            function watchEffect(effect) {
                activeEffect = effect;
                effect();
                activeEffect = null;
            }

            const targetMap = new WeakMap();

            function getDep(target, key) {
                // 添加整个对象的依赖
                let currMap = targetMap.get(target);

                if (!currMap) {
                    currMap = new Map();
                    targetMap.set(target, currMap);
                }

                // 获取具体key的依赖
                let dep = currMap.get(key);
                // 如果没有添加过则添加依赖
                if (!dep) {
                    dep = new Dep();
                    currMap.set(key, dep);
                }

                return dep;
            }

            const reactiveHandlers = {
                get(target, key, receiver) {
                    const dep = getDep(target, key);

                    // 为什么每次读取都要添加依赖,因为有可能会增加新的依赖
                    dep.depend();
                    return Reflect.get(target, key, receiver);
                },
                set(target, key, value, receiver) {                    
                    const dep = getDep(target, key);

                    // 类似于之前的Object.set
                    const ret = Reflect.set(target, key, value, receiver);

                    // 通知执行依赖
                    dep.notify();

                    // 因为proxy的set必须要返回一个boolean的值告诉设置成功与否,因此这里返回系统api的结果即可
                    return ret;
                }
            };

            /**
             * @description 给对象封装并返回响应式代理
             */
            function reactive(oldObj) {
                return new Proxy(oldObj, reactiveHandlers);
            }

            // 需要实现的效果类似于
            const state = reactive({
                count: 0
            });

            watchEffect(() => {
                console.log(state.count);
            });
            // 第一次应当输出0

            state.count += 1;
            // 此时应当输出1
        </script>
    </body>
</html>

可以看到效果:

技术分享图片

 

没有问题,关于代码中所使用的Reflect可能有些同学会有疑问,相关内容可以参见Reflect,需要注意的是,因为基于Proxy的响应式数据的响应式其实是在对象上,而非Vue2的对象属性,因此新增属性也没有问题。而且利用Proxy的handler中的方法,除了get和set,连判断属性是否存在的has等方法也都可以追踪和处理依赖,具体的可以去阅读前面所发的Proxy的MDN资料。

当然这里写的都是极为简单的没有考虑边界情况。但是毕竟我们所需要的是了解核心机制,边界情况并非核心机制了

 

创建一个简单的迷你Vue3-1

原文:https://www.cnblogs.com/gguoyu/p/13290495.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!