了解vue3响应式原理之前,先回顾一下vue2的响应式原理的一些弊端:
- 响应化过程需要递归遍历,消耗较大
- 新加或删除属性无法监听
- 数组响应化需要额外实现
- Map、Set、Class等无法响应式 修改语法有限制
而vue3.0使用ES6的Proxy特性来解决上面这些问题,下面我们通过Proxy实现一个响应式函数:
function reactive(obj) {
// Proxy只能接受一个对象
if (typeof obj !== 'object' && obj != null) {
return obj
}
// Proxy相当于在对象外层加拦截
const observed = new Proxy(obj, {
get(target, key, receiver) {
// Reflect用于执行对象默认操作,更规范、更友好
// Proxy和Object的方法Reflect都有对应
const res = Reflect.get(target, key, receiver)
console.log(`获取${key}:${res}`)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
console.log(`设置${key}:${value}`)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log(`删除${key}:${res}`)
return res
}
})
return observed
}
测试代码
const state = reactive({
name: '张三',
hobbies: { book: '编程' }
})
state.name // 获取name:张三
state.name = '李四' // 设置name:李四
state.age = 29 // 设置age:29
delete state.age // 删除age:true
state.hobbies.book // 获取hobbies:[object Object]
通过上面测试代码发现,reactive方法中的对象中如果还嵌套其它对象就不能正确get取值了,下面我们来解决这问题。
嵌套对象响应式
// 定义一个工具方法,在get取值的时候用于判断该值是否是一个对象。
const isObject = val => val !== null && typeof val === 'object'
function reactive(obj) {
// Proxy只能接受一个对象
if (!isObject(obj)) {
return obj
}
// Proxy相当于在对象外层加拦截
const observed = new Proxy(obj, {
get(target, key, receiver) {
// Reflect用于执行对象默认操作,更规范、更友好
// Proxy和Object的方法Reflect都有对应
const res = Reflect.get(target, key, receiver)
console.log(`获取${key}:${res}`)
// 在get取值的是否判断该值是否是一个对象,如果是则递归
return isObject(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
console.log(`设置${key}:${value}`)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log(`删除${key}:${res}`)
return res
}
})
return observed
}
// 测试代码
const state = reactive({
name: '张三',
hobbies: { book: '编程' }
})
state.hobbies.book // 获取book:编程
注:vue3.0中reactive函数要复杂的多,它里面有对于重复代理、新增或修改,新旧值等问题的处理,这里篇幅有限就不一一展开了。
通过完善,对象嵌套嵌套对象不能触发get的问题就解决了,下面来建立响应数据和更新函数之间的对应关系。
依赖收集
建立响应数据key和更新函数之间的对应关系,用法如下:
// 用户修改关联数据会触发响应函数
const state = reactive({name:'张三'})
state.name = '李四'
// 设置响应函数,当state.name改变此函数会更新。
effect(() => console.log(state.foo))
要实现上面的功能,我们先来实现三个函数:
- effect:将回调函数保存起来备用,立即执行一次回调函数触发它里面一些响应数据的getter
- track:getter中调用track,把前面存储的回调函数和当前target,key之间建立映射关系
- trigger:setter中调用trigger,把target,key对应的响应函数都执行一遍
** 1、创建effect函数 **
// 保存当前活动响应函数作为getter和effect之间桥梁
const effectStack = []
// effect任务:执行fn并将其入栈
function effect(fn) {
const rxEffect = function () { // 1.捕获可能的异常
try {
// 2.入栈,用于后续依赖收集
effectStack.push(rxEffect)
// 3.运行fn,触发依赖收集
return fn()
} finally {
// 4.执行结束,出栈
effectStack.pop()
}
}
// 默认执行一次响应函数
rxEffect()
// 返回响应函数
return rxEffect
}
** 2、track、trigger方法实现 **
// 映射关系表,结构大致如下:
// {target: {key: [fn1,fn2]}}
let targetMap = new WeakMap()
function track(target, key) {
// 从栈中取出响应函数
const effect = effectStack[effectStack.length - 1]
if (effect) {
// 获取target对应依赖表
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 获取key对应的响应函数集
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
if (!deps.has(effect)) {
deps.add(effect)
}
}
}
// 触发target.key对应响应函数
function trigger(target, key) {
// 获取依赖表
const depsMap = targetMap.get(target)
if (depsMap) {
// 获取响应函数集合
const deps = depsMap.get(key)
if (deps) {
// 执行所有响应函数
deps.forEach(effect => {
effect()
})
}
}
}
方法实现之后,我们只需要再Proxy构造函数中的get和set中进行依赖收集即可,下面是完整的代码:
const isObject = val => val !== null && typeof val === 'object'
function reactive(obj) {
// Proxy只能接受一个对象
if (!isObject(obj)) {
return obj
}
// Proxy相当于在对象外层加拦截
const observed = new Proxy(obj, {
get(target, key, receiver) {
// Reflect用于执行对象默认操作,更规范、更友好
// Proxy和Object的方法Reflect都有对应
const res = Reflect.get(target, key, receiver)
track(target, key)
console.log(`获取${key}:${res}`)
// 在get取值的是否判断该值是否是一个对象,如果是则递归
return isObject(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
trigger(target, key)
console.log(`设置${key}:${value}`)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log(`删除${key}:${res}`)
return res
}
})
return observed
}
// 保存当前活动响应函数作为getter和effect之间桥梁
const effectStack = []
// effect任务:执行fn并将其入栈
function effect(fn) {
const rxEffect = function () { // 1.捕获可能的异常
try {
// 2.入栈,用于后续依赖收集
effectStack.push(rxEffect)
// 3.运行fn,触发依赖收集
return fn()
} finally {
// 4.执行结束,出栈
effectStack.pop()
}
}
// 默认执行一次响应函数
rxEffect()
// 返回响应函数
return rxEffect
}
// 映射关系表,结构大致如下:
// {target: {key: [fn1,fn2]}}
let targetMap = new WeakMap()
function track(target, key) {
// 从栈中取出响应函数
const effect = effectStack[effectStack.length - 1]
if (effect) {
// 获取target对应依赖表
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 获取key对应的响应函数集
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
if (!deps.has(effect)) {
deps.add(effect)
}
}
}
// 触发target.key对应响应函数
function trigger(target, key) {
// 获取依赖表
const depsMap = targetMap.get(target)
if (depsMap) {
// 获取响应函数集合
const deps = depsMap.get(key)
if (deps) {
// 执行所有响应函数
deps.forEach(effect => {
effect()
})
}
}
}
// 测试代码
const state = reactive({ name: '张三' })
// 第一次取值打印出张三,当state.name修改之后,就打印出李四了
effect(() => console.log(state.name))
state.name = '李四'