原理:通过发布订阅来修改数据的值。
响应式
使用Object.defineProperty来实现。对对象的get/set进行重写。通过重写set的时候修改元素的值实现。
import React, { useEffect, useRef } from "react";
const Component = () => {
const input = useRef();
const text = useRef();
useEffect(() => {
// 实现双向绑定
input.current.addEventListener("keyup", function (e) {
obj.val = e.target.value
});
let obj = {}
Object.defineProperty(obj, 'val', {
get: function () { return obj.val },
set: function (_val) {
input.current.value = _val
text.current.innerHTML = _val
}
})
}, []);
return <>
<input type="text" ref={input}/>
你输入的文本是:
<p ref={text} />
</>
};
export default Component;
发布订阅
简单的发布订阅。就是收集订阅者,并执行订阅者的方法
/**
* 订阅者
*/
class Watcher {
callback: Function;
constructor(callback) {
this.callback = callback
}
public update() {
this.callback();
}
}
/**
* 依赖和订阅者收集
*/
class Dep {
private subs: Watcher[] = [];
constructor() {
this.subs = []
}
public add(watcher: Watcher) {
this.subs.push(watcher)
}
public notify() {
this.subs.forEach(watcher => {
watcher.update();
})
}
}
// test
let w1 = new Watcher(() => { console.log('第一个订阅者') })
let w2 = new Watcher(() => { console.log('第二个订阅者') })
let dep = new Dep();
dep.add(w1)
dep.add(w2)
dep.notify();
// 输出
// 第一个订阅者
// 第二个订阅者
vue实现
- 首先准备好模版和数据
<div id="app">
演示双向绑定
<h3>姓名:{{name}}</h3>
<h3>年龄:{{age}}</h3>
<h3>身份:{{info.a}}</h3>
修改名称:<input type="text" v-model="name" />
</div>
{
name: '大鹏',
age: 21,
info: {
a: "共产主义接班人",
}
}
- 然后编写Vue的主程序 主要是数据的监听和模版的编译
import Observer from './Observer';
import Compier from './Compier';
class Vue {
private $data;
constructor(options: any) {
this.$data = options.data;
// 递归监听内部数据
Observer(this.$data);
// 对this.$data.xxx换成this.xxx
this._()
// 编译模版
Compier(options.el, this);
}
private _() {
// 属性代理,方便直接取值
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return this.$data[key];
},
set(v) {
this.$data[key] = v;
},
})
});
}
}
export default Vue;
- 接下来看数据的响应式监听 对数据的每个key重定义get/set方法。初始get的时候添加到发布依赖,set的时候通过发布器通知所有订阅者更新。
import Dep from './Dep';
function Observer(obj: any) {
if(!obj || typeof obj !== 'object') return;
const dep = new Dep()
Object.keys(obj).forEach(key => {
let value = obj[key];
// 递归监听
Observer(value);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log(`取${key}的值:${value}`)
// 收集有哪些订阅者
// @ts-ignore
window._target_ && dep.add(window._target_);
return value
},
set(v) {
value = v;
// 重新监听
Observer(value)
//通知每个订阅者更新自己的文本
dep.notify()
}
})
})
}
export default Observer
- 接下来看下发布者如何实现
import Watcher from "./Watcher";
/**
* 订阅者(观察者)
*/
class Dep {
private subs: Watcher[] = [];
constructor() {
this.subs = []
}
public add(watcher: Watcher) {
this.subs.push(watcher)
}
public notify() {
this.subs.forEach(watcher => {
watcher.update();
})
}
}
export default Dep
- 再看订阅者如何实现 注意初始化的时候有调用get方法去添加依赖。并且使用_target_全局变量传递当前实例。
type Vue = any
/**
* 发布者(被观察者)
*/
class Watcher {
vm: Vue;
cb: Function;
key: string;
constructor(vm: Vue, key: string, cb: Function) {
this.vm = vm
this.key = key
this.cb = cb
// 被观察者放全局对象上
// @ts-ignore
window._target_ = this;
// 取值的时候回执行get,此时调用Dep添加依赖列表
key.split(".").reduce((obj, key) => obj[key], vm);
// @ts-ignore
window._target_ = null
}
public update() {
// 更新key指向的最细粒度的值
const value = this.key.split(".").reduce((obj, key) => obj[key], this.vm)
this.cb(value)
}
}
export default Watcher
- 最后写个组件调用试试
import { useEffect } from "react";
import Vue from './Vue';
const Component = () => {
useEffect(() => {
new Vue({
el: '#app',
data: {
name: '大鹏',
age: 21,
info: {
a: "共产主义接班人",
}
},
})
}, []);
return null
};
export default Component;
看看效果,还不错:
参考: 双向绑定原理以及实现:blog.csdn.net/qq_41998083…