vue 实现个简易版(2)

上一篇文章实现了模板数据展示到视图上面,这一篇来实现数据的双向绑定。

Watcher

实现一个Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

这里的实现需要结合发布订阅者模式。

1. Dep类的实现

完成对于依赖的收集和触发

class Dep{
	constructor(){
		this.subscribers = new Set()
	}
	addSub(sub){
		this.subscribers.add(sub)
	}
	notify(){
		this.subscribers.forEach(watcher => watcher.update());
	}
}

2. 结合Observer

在Dep.target不为空的时,设置geter的时候收集依赖,在数据变化的时候触发更新

...
defindReactive(obj,key,value){ 
    this.observer(value) //如果数据是一个对象,做成响应式
    let dep = new Dep()
    Object.defineProperty(obj,key,{
        get(){
            Dep.target && dep.addSub(Dep.target) // 添加
            return value
        },
        set:()=>{
            // 当赋的值和老值一样,就不重新赋值
            if (newVal != value) {
                this.observer(newVal)//新值,做成响应式
                value = newVal
                dep.notify() // 添加
            }
        }
    })
}
...

3. Watcher类的实现

刚开始需要保存一个老的状态

class Watcher{
	constructor(vm,expr,cb){
		this.vm = vm 
		this.expr = expr 
		this.cb = cb 
		this.value = this.getVal() // 刚开始需要保存一个老的状态
	}
	getVal(){
		Dep.target = this
		let value = CompilerUtil.getVal(this.vm,this.expr) //在 new Watcher 保存旧值 触发 dep 加入观察
		Dep.target = null
		return value
	}
    // 当状态发生改变后,会调用观察者的update方法来更新视图
	update(){
		let newVal = CompilerUtil.getVal(this.vm,this.expr)
		if(newVal !== this.value){
			this.cb(newVal)
		}
	}
}

4. 结合Compiler

  • 在上面1.2讲到,Dep.target不为空的时才会收集依赖,其实就是在new Watcher()的时候

  • 这里就需要在模板数据发生改变以前,对data上面的数据实例化成一个个的Watcher加入依赖

  • 模板数据发生改变的时刻,就是在Compiler中,使用CompilerUtil处理不同的指令的时候

分别修改CompilerUtil中的text和setTextContent函数

text(node,expr,vm,){
    let fn = this.updater['textUpdater']
    let content = this.getVal(vm, expr)
    new Watcher(vm, expr, (newValue)=>{//1.4 添加
        fn(node, newValue)
    })
    fn(node, content)
},
setTextContent(node,expr,vm) {
    let fn = this.updater['textUpdater']
    let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
        new Watcher(vm, args[1], (newValue)=>{//1.4 添加
            fn(node, newValue)
        })
        return this.getVal(vm, args[1])
    })
    fn(node, content)
},

简单测试一下 vm.address = '123' 会发现似乎并没有任何变化,setter也为触发,其实_proxyData代理的时候还需要设置setter

_proxyData(data){
		Object.keys(data).forEach((key)=>{
			Object.defineProperty(this,key,{
				get(){
					return data[key]
				},
				set(newValue){//1.4 添加
					return data[key] = newValue
				}
			})
		})
	}

5. v-model的实现

...
//1. html 添加输入框
<input type="text" v-model="school">

...

//2.CompilerUtil中添加model函数处理指令,同样需要加入依赖,并且触发依赖执行
//处理v-model指令
model(node,expr,vm){
    let fn = this.updater['modelUpdater']
    let content = this.getVal(vm, expr)
    //加入依赖
    new Watcher(vm, expr, (newVal) => {
        fn(node, newVal)
    })
    node.addEventListener('input', (e)=>{
        let value = e.target.value
        this.setVal(vm,expr,value)//触发依赖
    }, false)
    fn(node, content)
}
...

// 3.updater对象中添加
modelUpdater(node,value){
    node.value = value
}

...

//4.添加改变data数据值的方法
setVal(vm, expr, value) {
    expr.split('.').reduce((data, key, index, arr) => {
        if (index == arr.length - 1) {
            return data[key] = value  // 设置数据
        }
        return data[key]
    }, vm.$data)
}

目前为止实现了指令v-mode对于数据的双向绑定,下一章将拆分v-model的语法糖模式,实现更多指令等;

posted @ 2020-09-16 10:19  tang丶有年  阅读(145)  评论(0编辑  收藏  举报