Vue3设计与实现共读-响应系统(一)

311 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

响应系统的作用与实现

响应系统是Vue.js的重要组成部分。而响应式数据和副作用又是响应系统的重要组成部分。

响应式数据与副作用函数

function effect() {
	 document.body.innerText = 'hello vue3'
}

上面函数中,当effect执行便会影响到body中的内容,而除了effect之外,任何函数又都可以获取与设置body中的内容。简单来说就是effect函数的执行会直接或间接影响到其他函数的执行。这时我们就可以说effect产生了副作用。副作用的触发条件很简单,只要修改了全局变量,就算是一个副作用。

var a = 1
function effect(){
 a = 2 // 修改全局变量,产生副作用
}

我们知道了什么是副作用函数,那么什么是响应式数据呢?

const obj = { text: "hello world" }
function effect(){
	 // effect函数会读取obj.text
	document.body.innerText = obj.text
}

上述代码中,effect的执行会将body元素的innerText属性设置为obj.text。我们希望得到的是,当obj.text发生变化时,会自动执行effect函数。

如果能实现修改了obj.text的值,副作用函数能自动重新执行,那么我们就说obj对象是响应式的。

响应式数据的基本实现

想要让obj变成响应式数据,我们需要实现两点:

  • 当副作用effect执行时,会触发字段obj.text读取操作;
  • 当修改obj.text的值时,会触发字段obj.text设置操作。

如果我们能拦截到一个对象的读取与设置,一切就都变的简单明了。

将副作用存储到桶中图4-1.gif

当读取到obj.text时,我们将副作用存储到桶中,当设置obj.text时,再把副作用函数effect从桶中提取出来并执行。

把副作用从桶中取出 图4-2.gif

而对于最关键的拦截操作,我们如何才能拦截对象属性的读取和设置呢。在ES2015之前,我们只能通过Object.defineProperty实现,也就是Vue2的实现方式。而如今呢,我们可以用Proxy去实现,Vue3也是这么做的。

// 副作用函数
function effect() {
	document.body.innerText = obj.text
}

// 存储副作用函数的桶
const bucket = new Set()

// 原始数据
const data = { text: 'hello world' }
// 对原始数据代理
const obj = new Proxy(data, {
	// 拦截读取操作
	get(target, key) {
		// 将副作用函数effect存入副作用函数桶中
		bucket.add(effect)
		// 返回属性值
		return target[key]
	},
	// 拦截设置操作
	set(target, key, newValue) {
		// 设置属性值
		target[key] = newValue
		// 把副作用函数从桶中取出执行
		bucket.forEach((fn) => fn())
		// 返回执行成功
		return true
	},
})

我们先创建了一个用于存储副作用函数的桶bucket,将它设置为Set类型。然后定义原始数据data。obj是原始数据类型的代理对象,我们分别设置了set和get拦截函数,用于读取与设置的拦截。当读取时将副作用函数存储到桶中,当设置属性值时,将副作用从桶中取出并执行,这样我们就实现了一个简单的响应式数据。我们可以通过下面的代码测试一下。

setTimeout(() => {
	obj.text = 'hello vue3'
}, 1000)

以上就是一个简单的响应式数据的实现,还有这太多不完善的地方,现在我们是直接通过名字(effect)来获取的副作用函数,这样显然是不可取的,如果更换了副作用函数名,该响应式便失效了。那么如何去掉这种硬编码机制,如何理解响应式数据的基本实现和工作原理,我们在下一篇文章中再继续探讨。