demo:
const { reactive, effect } = VueObserver
const origin = {
count: 0
}
# 1. 将数据代理变为响应式数据
const state = reactive(origin)
# 2. 依赖响应式数据的 动作:Dep依赖
const fn = () => {
const count = state.count
console.log(`set count to ${count}`)
}
effect(fn) // 用effect包裹一下
# 3. 修改data,可以看到fn被执行
控制台执行 state.count++
输出 set count to 1
问题的点:
为啥引用了data的函数,还得用effect函数包裹一下才行?
触发的本身不知道么
effect: 主动执行了下这个函数,然后触发了getter,触发getter之后的track,生成依赖收集表
track
run:当setter的时候执行依赖收集表里的func
这里effect即是watcher的功能
而之前的dep有个专门的Dep类来管理这个一对多的依赖关系
现在都内化到了effect的track里,
effect管理
export const targetMap = new WeakMap();
export const effectStack = [];
回答下问题,为啥非要包裹一下
可能是,某个data的属性更改后,可以触发setter,但是无法得到触发这个动作的所在环境:当前函数体等
所以需要包裹了一下才能收集这个函数体
我的想法:
如果真的要用一个高阶函数包裹一下才能实现的话
那需要包裹的是,每个组件的render函数
怎么能高效的对每个类的render进行包裹呢
前两天看了个技术贴,正好将proxy的妙用,对类的函数进行包裹实现log的。。
我现在还想了解下vue的patch阶段
上面的响应式解决的问题是:
data->view:现在是data触发后,全局render
看了文章之后,
我可以:不是在变更时主动触发render了。
而是在每个组件的render那进行主动把自己监听,等待render的过程
这是解决思路1
还有解决思路是:还是全局render
但是render的时候,会对待更新的内容进行patch对比
负责对新旧 VNode 进行比对,并以合适的方式更新DOM,也就是我们常说的 patch
vue的patch就是diff找到更新的节点,dom进行更新的情况
但是对于canvas不适用
下面去strucV看下咋实现的
初始化阶段
- 把 origin 对象转化成响应式的 Proxy 对象 state。
- 把函数 fn() 作为一个响应式的 effect 函数。
当一个普通的函数 fn() 被 effect() 包裹之后,就会变成一个响应式的 effect 函数,
而 fn() 也会被立即执行一次。
由于在 fn() 里面有引用到 Proxy 对象的属性,所以这一步会触发对象的 getter,从而启动依赖收集。
除此之外,这个 effect 函数也会被压入一个名为”activeReactiveEffectStack“(此处为 effectStack)的栈中,供后续依赖收集的时候使用。
export function effect (fn) {
// 构造一个 effect
const effect = function effect(...args) {
return run(effect, fn, args)
}
// 立即执行一次
effect() // 立即执行一次,触发 data.getter 完成依赖收集
return effect
}
export function run(effect, fn, args) {
if (effectStack.indexOf(effect) === -1) {
try {
// 往池子里放入当前 effect
effectStack.push(effect)
// 立即执行一遍 fn()
// fn() 执行过程会完成依赖收集,会用到 effect
return fn(...args)
} finally {
// 完成依赖收集后从池子中扔掉这个 effect
effectStack.pop()
}
}
}
依赖收集
依赖收集阶段最重要的目的,就是建立一份”依赖收集表“
targetMap 是一个 WeakMap:
key:reactive(origin)里的origin,即代理前的原始数据
val:依赖该state的dep们
而dep们是用map结构维护的
key:getter当前触发的data属性,这里是state.count里的count属性
val:触发过该属性值所对应的各个 effect
如此建立起:{ target -> key -> dep } 的对应关系
// getter
export function track (target, operationType, key) {
const effect = effectStack[effectStack.length - 1]
if (effect) {
let depsMap = targetMap.get(target)
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (dep === void 0) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(effect)) {
dep.add(effect)
}
}
}
响应阶段
依赖收集表的作用在于:
当一个数据源属性更改时,会触发依赖该数据源的多个依赖更新;
当多个数据源属性更改时,触发一一对应的 多个依赖更新
比如下面的例子:
Proxy:{ count: 0, age: 18 }
当修改对象的某个属性值的时候,会触发对应的 setter。
下面就是 根据setter的key找到dep,执行dep的过程:
export function trigger (target, operationType, key) {
// 取得对应的 depsMap
const depsMap = targetMap.get(target)
if (depsMap === void 0) {
return
}
// 取得对应的各个 dep
const effects = new Set()
if (key !== void 0) {
const dep = depsMap.get(key)
dep && dep.forEach(effect => {
effects.add(effect)
})
}
// 简化版 scheduleRun,挨个执行 effect
effects.forEach(effect => {
effect()
})
}
扩:proxy数据监听的性能提升
实际上,Vue3 并非简单的通过 Proxy 来递归侦测数据, 而是通过 get 操作来实现内部数据的代理,并且结合 WeakMap 来对数据保存,这将大大提高响应式数据的性能。
rawToReactive 这个数据的代理也是递归的,只是他是weekMap,从而性能相对object要提高不少
const rawToReactive = new WeakMap()
const reactiveToRaw = new WeakMap()
vue3在对数据进行代理处理时候,还开了这两个map,是干啥的?