手写实现基础的reactive
github地址: https://github.com/feddiyao/Frontend-05-Template/tree/master/Week%2005
proxy的基本用法
看一下MDN对proxy的定义:Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
语法:
const p = new Proxy(target, handler)
所以根据proxy的特性,我们可以看到,使用了proxy,我们代码的预期性会变差。
看一下proxy的基本用法:
let object = {
a:1,
b:2
}
let po = new Proxy(object, {
set(obj, prop, val){
console.log(obj, prop, val);
}
})
通过对object包装proxy,我们可以通过po.a = 3
调用其中的set方法。
看下调用po.x = 5
的打印结果:
模仿reactive实现原理
vue中的reactive也是通过proxy来实现的,我们就把reactive的实现作为一个练习。
首先是实现基础的get和set方法:
let object = {
a:1,
b:2
}
let po = reactive(object)
function reactive(object) {
return new Proxy(object, {
set(obj, prop, val){
obj[prop] = val;
console.log(obj, prop, val);
return obj[prop];
},
get(obj, prop) {
console.log(obj, prop);
return obj[prop];
}
})
}
此时我们调用po.x = 99
页面打印结果如下:
然后是监听set导致的元素内容变化,我们看下基础实现,定义一个effect,它可以直接监听po上的属性,以代替事件监听的机制。在effdct中进行callback函数的入栈,看下代码:
let callbacks = []
let object = {
a:1,
b:2
}
let po = reactive(object)
effect(()=> {
console.log(po.a)
})
function effect(callback) {
callbacks.push(callback)
}
function reactive(object) {
return new Proxy(object, {
set(obj, prop, val){
obj[prop] = val;
console.log(obj, prop, val);
for (let callback of callbacks) {
callback();
}
return obj[prop];
},
get(obj, prop) {
console.log(obj, prop);
return obj[prop];
}
})
}
po.x = 5
看下控制台的打印结果:
接下来我们来看一下,如何查看一个函数实际用了哪些变量,我们可以在reactive变量的get方法中获得一个监听的效果。
先对effect方法做一个基本的修改:
let callbacks = [];
let useReactivities = [];
let object = {
a:1,
b:2
}
let po = reactive(object)
effect(()=> {
console.log(po.a)
})
function effect(callback) {
//callbacks.push(callback)
useReactivities = [];
callback();
console.log(useReactivities);
}
function reactive(object) {
return new Proxy(object, {
set(obj, prop, val){
obj[prop] = val;
console.log(obj, prop, val);
for (let callback of callbacks) {
callback();
}
return obj[prop];
},
get(obj, prop) {
useReactivities.push([obj, prop]);
return obj[prop];
}
})
}
看下运行效果图:
下面补全effect代码:
let callbacks = new Map();
let useReactivities = [];
let object = {
a:1,
b:2
}
let po = reactive(object)
effect(()=> {
console.log(po.a)
})
function effect(callback) {
//callbacks.push(callback)
useReactivities = [];
callback();
console.log(useReactivities);
for (let reactivity of useReactivities) {
if (!callbacks.has(reactivity[0])) {
callbacks.set(reactivity[0], new Map())
}
if (!callbacks.get(reactivity[0]).has(reactivity[1])) {
callbacks.get(reactivity[0]).set(reactivity[1], [])
}
callbacks.get(reactivity[0]).get(reactivity[1]).push(callback);
}
}
function reactive(object) {
return new Proxy(object, {
set(obj, prop, val){
obj[prop] = val;
if (callbacks.get(obj) && callbacks.get(obj).get(prop))
for (let callback of callbacks.get(obj).get(prop)) {
callback();
}
return obj[prop];
},
get(obj, prop) {
useReactivities.push([obj, prop]);
return obj[prop];
}
})
}
此时当我们运行po.a = 1
会触发effect效果,但是 po.b=1
就不会触发effect,达到了我们想要实现的监听effect函数引用变量的效果。
但是现在这个函数还是没有那么完美,试i想一下如果object当中的a为一个对象,那么我们在effect中调用po.a.b
显然是监听不到的,我们需要继续进行优化。
更新reactive的方法:
function reactive(object) {
if (reactivities.has(object))
return reactivities.get(object);
let proxy = new Proxy(object, {
set(obj, prop, val){
obj[prop] = val;
if (callbacks.get(obj) && callbacks.get(obj).get(prop))
for (let callback of callbacks.get(obj).get(prop)) {
callback();
}
return obj[prop];
},
get(obj, prop) {
useReactivities.push([obj, prop]);
if(typeof obj[prop] === "object")
return reactivity(obj[prop])
return obj[prop];
}
})
reactivities.set(object, proxy);
return proxy;
}
此时我们调用po.a.b
是能够监听到变化的,那么reactive的主要实现逻辑就在上面了。
reactive基础上实现调色盘case
我们在刚才实现的reactive基础上实现一个调色盘的case,看下总的代码:
<input id ="r" type="range" min=0 max=255/>
<input id ="g" type="range" min=0 max=255/>
<input id ="b" type="range" min=0 max=255/>
<div id="color" style="width:100px;height:100px;">
</div>
<script>
let callbacks = new Map();
let reactivities = new Map();
let useReactivities = [];
let object = {
r: 1,
g: 1,
b: 1
}
let po = reactive(object)
effect(()=> {
document.getElementById("r").value = po.r;
})
effect(()=> {
document.getElementById("g").value = po.g;
})
effect(()=> {
document.getElementById("b").value = po.b;
})
document.getElementById("r").addEventListener("input", event => po.r = event.target.value);
document.getElementById("g").addEventListener("input", event => po.g = event.target.value);
document.getElementById("b").addEventListener("input", event => po.b = event.target.value);
effect(()=> {
document.getElementById("color").style.backgroundColor = `rgb(${po.r}, ${po.g}, ${po.b})`;
})
function effect(callback) {
//callbacks.push(callback)
useReactivities = [];
callback();
console.log(useReactivities);
for (let reactivity of useReactivities) {
if (!callbacks.has(reactivity[0])) {
callbacks.set(reactivity[0], new Map())
}
if (!callbacks.get(reactivity[0]).has(reactivity[1])) {
callbacks.get(reactivity[0]).set(reactivity[1], [])
}
callbacks.get(reactivity[0]).get(reactivity[1]).push(callback);
}
}
function reactive(object) {
if (reactivities.has(object))
return reactivities.get(object);
let proxy = new Proxy(object, {
set(obj, prop, val){
obj[prop] = val;
if (callbacks.get(obj) && callbacks.get(obj).get(prop))
for (let callback of callbacks.get(obj).get(prop)) {
callback();
}
return obj[prop];
},
get(obj, prop) {
useReactivities.push([obj, prop]);
if(typeof obj[prop] === "object")
return reactivity(obj[prop])
return obj[prop];
}
})
reactivities.set(object, proxy);
return proxy;
}
</script>
效果图: