引言
Vue
的响应式系统是框架的重要组成部分,之后会在资料的辅助下逐步实现一个简洁的响应式数据结构
副作用函数Effect
首先要了解一下副作用函数的概念,在函数式编程中副作用函数指在一定作用上对,对外部环境造成了一定的影响,如:
- 修改了函数作用域以外数据
let count = 0;
const add = () => {
count++
}
- 抛出异常
const throwErr = () => {
throw new Error()
}
响应式数据
当我们想要一个数据进行改变时,所有依赖于它的函数等,重新执行,如一个绑定于DOM
的数据,随之发生改变,这个数据我们就希望他是响应式的,Vue2
中通过Object.defineProperty
对数据进行劫持实现,Vue3
中则通过Proxy
对象实现
// vue2
// 原始数据
const data = {
age: 18,
}
let value = data.age
Object.defineProperty(data, "age", {
get() {
return value
},
set(newValue) {
value = newValue
},
})
// 修改数据
setTimeout(() => {
data.age++
}, 2000)
上面代码在两秒后会改变
age
属性的值,并且触发Setter
下面是Vue3
响应数据的基础实现
// Vue3
// 原始数据
const data = {
age: 18,
}
const proxy = new Proxy(data, {
get(target, key: keyof typeof data) {
return target[key]
},
set(target, key: keyof typeof data, newValue: never) {
target[key] = newValue
console.log("=========", target[key])
return true
},
})
// 修改数据
setTimeout(() => {
proxy.age++
}, 2000)
上面对原始数据
data
进行了代理,并设置了Setter
与Getter
,之后在使用代理后的对象数据进行操作即可
实现基础操作
接下来结合二者完成一个基础的响应式操作(Vue3
):
HTML
中创建两个元素#app
用于展示数据,.btn
用于修改数据
<!-- dom元素 -->
<div id="app"></div>
<button class="btn">改变数据</button>
- 创建对象并进行代理,使用代理后的数据展示于
#app
中
// 原始数据
const data = {
name: "zhangsan",
age: 18,
sex: "男",
}
// 进行代理 Vue3
const proxy = new Proxy(data, {
get(target, key: keyof typeof data) {
return target[key]
},
set(
target,
key: keyof typeof data,
newValue: (typeof data)[keyof typeof data]
) {
;(target[key] as any) = newValue
console.log("=========", target[key])
return true
},
})
const app = document.querySelector("#app")!
app.innerHTML = `<div>name: ${proxy.name}: age: ${proxy.age}</div>`
// 修改数据
const btn = document.querySelector(".btn")!
btn.addEventListener("click", () => {
proxy.age++
})
到这儿已经可以改变age的值了,但是还不能促使
DOM
的更新,因为并没有对修改DOM
的操作进行触发,所以接下来需要存储一下操作,并在修改属性值时进行调用
- 创建副作用函数,并进行存储,最后代码如下
// 存储副作用函数的桶
const buket = new Set<Function>()
// Getter中添加effect,Setter中执行effect
const data = {
name: "zhangsan",
age: 18,
sex: "男",
}
const proxy = new Proxy(data, {
get(target, key: keyof typeof data) {
buket.add(effect) // 添加副作用函数
return target[key]
},
set(
target,
key: keyof typeof data,
newValue: (typeof data)[keyof typeof data]
) {
;(target[key] as any) = newValue
buket.forEach((fn) => fn()) // 执行所有的副作用函数
return true
},
})
const effect = () => {
app.innerHTML = `<div>name: ${proxy.name}: age: ${proxy.age}</div>`
}
effect()
// 修改数据
const btn = document.querySelector(".btn")!
btn.addEventListener("click", () => {
proxy.age++
})
效果如下
点击按钮时,age
属性值发生改变,并且视图也发生变化
结束语
这实现了最基本的一个响应式效果,但还存在很多问题,例如属性没对应上,effect函数固定,后面再学习完善哇