响应式数据前瞻
不智能的数据
我们正常编写js代码的时候,变量值的改变不会触发相关视图依赖更新,如下面代码所示。
const reactive = {
a: 'hello'
}
function effect() {
document.body.innerText = reactive.a
}
effect()
reactive.a = 'edited' // 变量修改,不会触发effect函数执行,需要手动调用
effect() // 手动调用,更新视图
Vue中智能的数据
在Vue编写的数据,当数据变化时,相关的视图也会实时更新,官方称之为响应式数据,实现响应式数据的核心就是数据劫持和发布订阅。一句话概括就是通过劫持数据的获取(getter)操作来收集依赖,劫持数据的设置(setter)操作来触发依赖,其中收集依赖和触发依赖就是发布订阅模式的核心思想。
在Vue2中,数据劫持是通过Object.defineProperty来完成对象属性的获取(getter)和设置(setter)操作;Vue3则是通过Proxy + Reflect来完成对象属性相关劫持操作。下面可以使用Proxy来简单实现一个响应式数据。
// 当前被激活的依赖
let activeEffect = null
function effect(fn) {
activeEffect = fn
const result = fn()
activeEffect = null
return result
}
// 用于保存依赖的对应关系 target -> key -> effect
const targetMap = new WeakMap()
// 依赖收集
function track(target, key) {
// 当前没有被激活的依赖,直接退出
if (!activeEffect) return
// 先获取 target 映射的依赖收集器
let depsMap = targetMap.get(target)
if (!depsMap) targetMap.set(target, (depsMap = new Map()))
// 再获取 key 映射的依赖,里面用于存放依赖
let deps = depsMap.get(key)
if (!deps) depsMap.set(key, (deps = new Set()))
if (!deps.has(activeEffect)) deps.add(activeEffect)
}
// 触发依赖
function trigger(target, key, newValue, oldValue) {
// 值没变化直接退出
if (newValue === oldValue) return
const depsMap = targetMap.get(target)
if (!depsMap) return
const deps = depsMap.get(key)
if (!deps) return
const effects = [...deps]
effects.forEach(effect => effect())
}
// 把普通对象变成响应式对象
function reactive(originData) {
const reactiveData = new Proxy(originData, {
get(target, key, receiver) {
// 收集当前激活的依赖
track(target, key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
// 触发对应的依赖
trigger(target, key, value, oldValue)
return result
}
})
return reactiveData
}
// 定义一个响应式对象
const originData = {
name: 'f1ower1ang',
age: 18
}
const reactiveData = reactive(originData)
// f1ower1ang-18
effect(() => {
document.body.innerText = `${reactiveData.name}-${reactiveData.age}`
})
// edited-f1ower1ang-18
setTimeout(() => {
reactiveData.name = 'edited-f1ower1ang'
}, 1000)
可以把上面的内容复制到浏览器控制台中运行一下,可以发现body元素起始内容为f1ower1ang-18,过了一秒后会自动更新为edited-f1ower1ang-18。这就是响应式数据的魅力所在,不需要我们修改完数据后手动操作去触发视图更新。
上述代码中,WeakMap和Proxy就是发布订阅模式的核心,其中WeakMap负责收集事件的订阅,Proxy中的getter和setter负责订阅事件和触发事件执行,这里的事件就是指传入effect中的函数参数,在Vue中则是和挂载组件以及组件更新相关的函数。
小结
本文从不具备响应式的数据作为切入点,到手写一个简单的响应式数据系统,为接下来的源码阅读打基础,下节则直接阅读源码,彻底掌握Vue3的响应式数据。