Vue3.0Proxy实现双向绑定指令

平常在vue中使用v-model使用较多,下面来看一下实现简易原理。

创建全局ob视图,结合观察、订阅模式完成双向绑定。

 <div id="app">
       <input type="text" v-model='message'  />
 </div>  
 <script>
const app = new Reactive({
    el: '#app',
    data() {
        return {
            message: 'hello'
        }
    }
})
 class Reactive {}
 </script>

首先,把挂载点确定好,把id#app传入类Reactive中,data是已经确定好的数据。

constructor({ el, data }) {
    const _data = data();
    this.el = document.querySelector(el);//设置挂载点
    this._ob = this.createObserver();//创建视图
    this.restoreProxy(_data);//设置代理对象
    return this.proxy
}

constructor接收到挂载点id,和元数据。创建el挂载点给到Reactive里边。

createObserver() {
    const _this = this;
    return {
         watchers: {},//观察者列表,保存的映射关系
         addWatchers(key, cb) {//添加任务视图,映射关系
            if (this.watchers[key]) {
                this.watchers[key].push(cb);
            } else {//初始化的时候没有任务,先添加一个
                this.watchers[key] = [cb];
            }
    	 },
          subscribe(key) {//订阅
            _this.el
                .querySelectorAll(`[v-model="${key}"]`)
                .forEach(item => {
                    const cb = text => item.value = text;//指令对应的任务方法
                    item.addEventListener('input', () => {
                        _this.proxy[key] = item.value;
                    });
                    cb(_this.proxy[key]);// 初始化数据,第一次调用任务方法
                    this.addWatchers(key, cb);//添加到队列里
                });
        },
    }
}

创建ob任务视图,在任务视图里添加观察者列表,保存的映射关系。在具体订阅每个指令和节点之间的关系、指令处理方法。

通过querySelectorAll的方式查询到挂载点下所有自定义属性v-mode节点,本例中只有input输入框节点,每次修改inputvalue即可,创建任务方法。

在把任务方法添加到观察者列表watchers中去。

restoreProxy(data) {
    //设置代理对象,构建关系
    this.proxy = new Proxy(data, {
        get: (target, key) => {
            return target[key]
        },
        set: (target, key, value) => {
            target[key] = value;
            this._ob.emit(key);
            return true;
        }
    });

    for (let key in data) {
        this._ob.subscribe(key);
    }
}

设置代理对象,构建关系,接收第一次传入的初始数据data,通过代理对象的形式设置gettersetter,让每一次的访问和修改都能察觉到,并作出处理。

迭代data,去订阅data与节点之间的关系,初始化任务绑定好数据。这个时候已经能看到input输入框里有hello了。

我们在创建订阅任务时,给input绑定了一个oninput事件,每次输入input都会触发proxy的set,更新了message,在去更新与message有关的节点。

createObserver() {//ob 任务队列
    const _this = this;
    return {
        watchers: {},//观察者列表,保存的映射关系
        addWatchers(key, cb) {//添加任务视图,映射关系
            if (this.watchers[key]) {
                this.watchers[key].push(cb);
            } else {//初始化的时候没有任务,先添加一个
                this.watchers[key] = [cb];
            }
        },
        subscribe(key) {
                _this.el
                .querySelectorAll(`[v-model="${key}"]`)
                .forEach(item => {
                    const cb = text => item.value = text;//指令对应的任务方法
                    item.addEventListener('input', () => {
                        _this.proxy[key] = item.value;// 这里会触发proxy 的set方法
                    });
                    cb(_this.proxy[key]);
                    this.addWatchers(key, cb);//添加到队列里
                });
            	_this.el
                .querySelectorAll(`[data-on="${key}"]`)
                .forEach(item => {
                    const cb = text => item.innerHTML = text;//每一个不同的指令对应不同的任务方法
                    cb(_this.proxy[key])
                    this.addWatchers(key, cb);//添加到队列里
                });
        },
        emit(key) {//添加需要更新的任务
            this.task.add(key);
            this.update();
        },
        update() {
            // 异步刷新执行任务,不重复刷新视图。
            if (this.isUpdate) { return }
            this.isUpdate = true;
            Promise.resolve().then(() => {
                for (let key of this.task) {
                    this.watchers[key].forEach(cb => {
                        cb(_this.proxy[key]);
                    });
                }
                this.isUpdate = false;//恢复状态
                this.task.clear();//清空任务
            });
        },
        task: new Set(),// ES6set,数据不会重复
        isUpdate: false,// 状态开关
    }
}

在来创建一个data-on的指令来验证是否会随着message的变化而变化。也是同样的方试查找到节点,创建对应任务方法,放到观察队列里。

在修改message时,某些情况下会去改变与之关联的数据,我们把所有需要改变的任务通过set方式收集在一起,最后在异步刷新数据。

完整代码如下:

<div id="app">
    <div data-on="message"></div>
    <input type="text" v-model='message' />
</div>
<script>
    class Reactive {
        constructor({ el, data }) {
            const _data = data();
            this.el = document.querySelector(el);
            this._ob = this.createObserver();//创建视图
            this.restoreProxy(_data);//设置代理对象
            return this.proxy
        }
        createObserver() {//ob 任务队列
            const _this = this;
            return {
                watchers: {},//观察者列表,保存的映射关系
                addWatchers(key, cb) {//添加任务视图,映射关系
                    if (this.watchers[key]) {
                        this.watchers[key].push(cb);
                    } else {//初始化的时候没有任务,先添加一个
                        this.watchers[key] = [cb];
                    }
                },
                subscribe(key) {
                    _this.el
                        .querySelectorAll(`[v-model="${key}"]`)
                        .forEach(item => {
                            const cb = text => item.value = text;//指令对应的任务方法
                            item.addEventListener('input', () => {
                                _this.proxy[key] = item.value;// 这里会触发proxy 的set方法
                            });
                            cb(_this.proxy[key]);
                            this.addWatchers(key, cb);//添加到队列里
                        });
                    _this.el
                        .querySelectorAll(`[data-on="${key}"]`)
                        .forEach(item => {
                            const cb = text => item.innerHTML = text;//每一个不同的指令对应不同的任务方法
                            cb(_this.proxy[key])
                            this.addWatchers(key, cb);//添加到队列里
                        });
                },
                emit(key) {//添加需要更新的任务
                    this.task.add(key);
                    this.update();
                },
                update() {
                    // 异步刷新执行任务,   保证重复刷新视图。
                    if (this.isUpdate) { return }
                    this.isUpdate = true;
                    Promise.resolve().then(() => {
                        for (let key of this.task) {
                            this.watchers[key].forEach(cb => {
                                cb(_this.proxy[key]);
                            });
                        }
                        this.isUpdate = false;//恢复状态
                        this.task.clear();//清空任务
                    });
                },
                task: new Set(),// ES6set,保证数据不会重复
                isUpdate: false,// 状态开关
            }
        }
        restoreProxy(data) {
            //设置代理对象,构建关系
            this.proxy = new Proxy(data, {
                get: (target, key) => {
                    return target[key]
                },
                set: (target, key, value) => {
                    target[key] = value;
                    this._ob.emit(key);
                    return true;
                }
            });

            for (let key in data) {
                this._ob.subscribe(key);
            }
        }
    }

    const app = new Reactive({
        el: '#app',
        data() {
            return {
                message: 'hello'
            }
        }
    })
</script>

这个时候可以看到修改input绑定的message,div通过data-on绑定的也会随之变化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值