
demo:
const { reactive, effect } = VueObserverconst origin = {count: 0}# 1. 将数据代理变为响应式数据const state = reactive(origin)# 2. 依赖响应式数据的 动作:Dep依赖const fn = () => {const count = state.countconsole.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) {// 构造一个 effectconst 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 {// 往池子里放入当前 effecteffectStack.push(effect)// 立即执行一遍 fn()// fn() 执行过程会完成依赖收集,会用到 effectreturn fn(...args)} finally {// 完成依赖收集后从池子中扔掉这个 effecteffectStack.pop()}}}

依赖收集
依赖收集阶段最重要的目的,就是建立一份”依赖收集表“
targetMap 是一个 WeakMap:
key:reactive(origin)里的origin,即代理前的原始数据
val:依赖该state的dep们
而dep们是用map结构维护的
key:getter当前触发的data属性,这里是state.count里的count属性
val:触发过该属性值所对应的各个 effect

如此建立起:{ target -> key -> dep } 的对应关系
// getterexport 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) {// 取得对应的 depsMapconst depsMap = targetMap.get(target)if (depsMap === void 0) {return}// 取得对应的各个 depconst effects = new Set()if (key !== void 0) {const dep = depsMap.get(key)dep && dep.forEach(effect => {effects.add(effect)})}// 简化版 scheduleRun,挨个执行 effecteffects.forEach(effect => {effect()})}
扩:proxy数据监听的性能提升
实际上,Vue3 并非简单的通过 Proxy 来递归侦测数据, 而是通过 get 操作来实现内部数据的代理,并且结合 WeakMap 来对数据保存,这将大大提高响应式数据的性能。
rawToReactive 这个数据的代理也是递归的,只是他是weekMap,从而性能相对object要提高不少
const rawToReactive = new WeakMap()const reactiveToRaw = new WeakMap()vue3在对数据进行代理处理时候,还开了这两个map,是干啥的?
