响应式系统
什么是响应式系统?如下代码:
const info = {
name: 'caohan',
age: 18,
height: 1.8,
counter: 10
}
function doubleCounter() {
console.log(info.counter * 2)
}
...
doubleCounter 函数对 info.counter 有依赖。当 info.counter 的值改变时,这个函数并不会重新执行。
响应式即:只要 info.counter 的值改变,所有对它有依赖的地方都会重新执行。即:
// 当 info.counter++ 时,应该执行以下代码:
doubleCounter()
...
以以上代码为例,我们一步一步的实现响应式的系统。
实现思路:收集依赖、监听改变、执行依赖
首先第一步,我们构造一个类并把所有依赖加入订阅:
// 依赖相关类
class Dep {
constructor() {
// 订阅者:存储依赖
this.subscribers = []
// this.subscribers = new Set()
}
// 添加依赖
addEffect(effect) {
this.subscribers.push(effect)
// this.subscribers.add(effect)
}
notify() {
// 执行所有的依赖
this.subscribers.forEach((effect) => {
effect()
})
}
}
const dep = new Dep()
// 把所有的依赖加入订阅
dep.addEffect(doubleCounter)
...
当数据改变时,我们就直接调用依赖的执行方法:
// 数据改变
info.counter++
// 执行所有依赖
dep.notify()
这样做的好处是我们不用一个个的调用依赖的函数,缺点是不管是收集依赖还是调用都是手动来执行的。
为了实现自动收集依赖及数据改变时自动调用依赖,我们需要对以上代码做一个改造。
实现依赖的自动分类和自动调用
定义依赖相关类
// 定义依赖类
class Dep {
constructor() {
this.subscribers = []
}
// 添加依赖
depend() {
if (activeEffect) {
this.subscribers.push(activeEffect)
}
}
// 执行所有的依赖
notify() {
this.subscribers.forEach((effect) => {
effect()
})
}
}
定义添加依赖的函数
let activeEffect = null
// 定义添加依赖的函数
function watchEffect(effect) {
activeEffect = effect
activeEffect = null
effect() // 传入时默认执行一次
}
封装根据不同属性获取对应依赖的工具函数
const targetObj = {}
function getDep(target, key) {
let depsObj = targetObj[target]
if (!depsObj) {
depsObj = {}
targetObj[target] = depsObj
}
let dep = depsObj[key]
if (!dep) {
dep = new Dep()
depsObj[key] = dep
}
return dep
}
数据劫持函数
function reactive(raw) {
// vue2 实现
Object.keys(raw).forEach(key => {
const dep = getDep(raw, key)
let value = raw[key]
Object.defineProperty(raw, key, {
get() {
dep.depend()
return value
},
set(newVal) {
if (value !== newVal) {
value = newVal
dep.notify()
}
}
})
})
return raw
// vue3实现
return new Proxy(raw, {
get(target, key) {
const dep = getDep(target, key)
dep.depend()
return target[key]
},
set(target, key, newVal) {
const dep = getDep(target, key)
target[key] = newVal
dep.notify()
}
})
}
数据处理及添加依赖
const info = reactive({ counter: 100, price: 10 })
const foo = reactive({ name: 'kobe' })
watchEffect(function () {
console.log(1, info.counter);
})
watchEffect(function () {
console.log(2, info.price);
})
watchEffect(function () {
console.log(3, info.name);
})
info.counter++