vue3 数据响应式整体思路
- 使用new Proxy对响应式数据进行代理
- get函数
- 触发trace添加依赖
- 如果get的数据是对象,执行递归new Proxy代理(此处是懒调用,只有对象被使用才会执行new Proxy代理)
- set函数
- 触发trigger执行get函数收集的相关依赖
vue3响应式和vue2的变化
-
从Object.definePrototype =》 new Proxy
- 优点
- 对整个对象使用Proxy代理,并且对象是懒调用(未被使用的对象,不会添加代理),免去整个对象逐个key遍历的性能消耗
- 数组不需要特殊处理(vue2中重写 push pop shift unshift reverse sort splice 七个方法)
- 能给监听ES6的新对象 如 Map Set之类
- 缺点
- 不兼容ie,如果想使用composition api可以等vue2.7版本的更新
-
依赖收集
- vue2
- Wacher Dep deps,具体可以参考这篇文章https://editor.csdn.net/md/?articleId=118404927
- vue3
- effect的概念,代表某个动作引起的额外效果/影响。(当effect执行后,会建立起数据和fn的依赖关系,数据变更后触发fn执行)
- 依赖关系建立过程
- effect(fn)(在mount挂载的时候,对把update作为参数fn执行effect函数)
- fn入栈(effectStack)
- 执行一次fn
- fn函数中使用响应式数据,触发依赖(get)
- 触发trace函数,存储依赖
- targetMap.get(target)=>depMap.get(key)=>deps。 fn存入deps中
- effectStack出栈,结束依赖关系建立
- 数据变更触发更新
- 响应式数据更新,触发set函数
- 触发trigger函数
- targetMap.get(target)=>depMap.get(key)=>deps。遍历deps并且执行(deps内存的为所有的依赖)
- vue2
依赖收集原理图
vue3响应式简易代码
const isObject = v => typeof v === 'object'
function reactive(obj) {
if (!isObject(obj)) {
return obj
}
return new Proxy(obj, {
// target代理后的对象, 当递归之后target可能是子对象
get(target, key) {
console.log('get key', key)
// const res = target[key]
// 触发依赖收集
const res = Reflect.get(target, key) // 放着程序崩溃,优雅解决
// vue2 这里是往watcher添加依赖
// vue3 根据target key 存储再一个全局的依赖关系内
// targetMap => depMap => deps
trace(target, key)
// reactive递归,懒调用,被使用到,才会加入响应式
return isObject(res) ? reactive(res) : res
},
set(target, key, val) {
console.log('set key', key)
target[key] = val
const res = Reflect.set(target, key, val) // 返回设置是否成功
trigger(target, key)
return res
},
deleteProperty(target, key) {
console.log('dele ', key)
// delete target[key]
const res = Reflect.deleteProperty(target, key)
return res
}
})
}
const state = reactive({
foo: 'fooo',
count: 0,
bar: {
baz: 1
}
})
// 临时存储副作用函数
const effectStack = []
// 建立副作用 依赖关系
function effect(fn) {
const e = createReactiveEffect(fn)
// e()
return e
}
function createReactiveEffect(fn) {
try {
// 类似于Dep.target 全局变量
effectStack.push(fn)
return fn()
} finally {
effectStack.pop(fn)
}
}
// 保存依赖关系数据结构
const targetMap = new WeakMap()
// 依赖收集:建立target key fn银蛇关系
function trace(target, key) {
// 1. 获取effect
const effect = effectStack[effectStack.length - 1]
if (effect) {
// 2. 获取target对应的map
let depMap = targetMap.get(target)
if (!depMap) {
depMap = new Map()
targetMap.set(target, depMap)
}
// 3. 获取对应set
let deps = depMap.get(key)
if (!deps) {
deps = new Set()
depMap.set(key, deps)
}
// 4. effect存入deps
deps.add(effect)
}
if (!targetMap.has(target)) {
targetMap.set(target, new Map())
}
}
// 触发副作用:根据targe key 获取相关fns ,执行
function trigger(target, key) {
const depMap = targetMap.get(target)
if (depMap) {
const deps = depMap.get(key)
if (deps) {
deps.forEach(dep => dep())
}
}
}
/**
* 建立依赖关系
* 1. 执行effect(fn)
* 2. fn入栈(effectStack)
* 2. 首次先执行fn
* 3. 触发依赖 (state.foo)
* 4. 触发trace
* 5. targetMap.get(target)=>depMap.get(key)=>deps, deps存入fn
* 6. fn出栈(effectStack)
*/
effect(() => {
// 首次会先执行一次, 触发state.foo的get(此时effectStack内存在当前函数)
console.log('effect1', state.foo)
// 当effect嵌套执行的时候,effectStack会存在多个值,但是每次都从栈顶(数组最后一个 )拿数据
//effect(() => {
// console.log('effect3', state.foo)
//})
// 嵌套的话打印日志情况
//get key foo
//effect1 fooo
//get key foo
//effect3 fooo
//get key foo
//effect2 fooo
//get key foo
//set key foo
//get key foo
//effect1 foooooooo
//get key foo
//effect3 foooooooo
//get key foo
//effect3 foooooooo
//get key foo
//effect2 foooooooo
//get key foo
//effect3 foooooooo
})
effect(() => {
console.log('effect2', state.foo)
})
state.foo
state.foo = 'foooooooo'
// 日志打印情况
// get key foo 执行effect1内的函数,执行state.foo触发get函数
// effect1 fooo 打印effect1的日志
// get key foo 执行effect2内的函数,执行state.foo触发get函数
// effect2 fooo 打印effect2的日志
// get key foo 执行state.foo,触发get函数
// set key foo 执行state.foo = 'foooooooo',触发set函数,紧接着触发trigger函数执行所有依赖的fn
// get key foo effect1内的函数被执行,执行state.foo,触发get函数
// effect1 foooooooo
// get key foo effect2内的函数被执行,执行state.foo,触发get函数
// effect2 foooooooo
从上面代码和测试用例来看,就完成了effect和数据的依赖,数据的响应可以触发effect函数的执行