effect
这个匹配vue2 watch
Vue3的源代码并没有把effect暴露出去。
我们可以执行npm run build 就可以把所有的包都导出。
然后去dist目录创建一个example 文件件 index.html
然后引入 ./reactivity.global.js
介绍使用方法
let {effect, reactive} = VueReactivity; // npm run dev 构建vue是拿不到这个的 VueReactivity 源代码打包暴露出来的
// 这个方法有啥用? 这个是响应式的一个核心
// demo01
let state = reactive({a:1, other:1});
// 默认上来会执行一次。
effect(() => {
// 默认上来会执行一次。
// 取值会调用代理get方法。 这个时候可以把effect函数存放起来。 采用 WeakMap(target=>Map(key=>Set([effect])))
});
// 更新 state.a 这个的时候会让effect这个函数在跑起来
// state.a. 就触发了state的getter 出发了effect 的收集工作。
console.log(state.a);
setTimeout(() => {
// 上面那个effect函数就重新执行了, 读取的时候已经存储了effect 所以更新的时候可以直接执行它。所以数据一变就重新执行effect了 等价于vue2的watch react 是每次render才走对应钩子 这个是纯靠响应式。
state.a = 2;
});
// 这时候有一个问题我更改 other 的时候 effect需要重新执行吗? 应为effect内部other并没有使用。
// 这里只有 a 和 effect关联起来了
// [update 生命周期任何内容变了都会执行。// effect内部的属性和effect有关联]
// 疑问?自定义一下effect 看看这个玩意怎么和proxy关联的?!
基于上节课动了几个文件
// basehander.ts 需要改了getter 和 setter
// effect.ts effect 响应收集的核心
basehander.ts
主要修改了
const Getter = (isReadOnly:boolean=false, isShallow:boolean=false) => {
// 如果不是只读的那么就需要收集依赖关系
// 收集依赖 如果不是只读的需要收集起来一会更新试图,如果是只读的就不牵扯到视图的更新。
// 执行effect的时候里面取值会走到这里的。因为动用到了get
// 如果进来取值的时候需要收集effect
if(!isReadOnly){
// 问题是怎么拿到effect?
// 这个问题很简单把track的effect的收集器扔到effect.ts里面 这样在effect执行的时候可以放到全局里面后面回详细说明
// track调用的是effect的track
// 这个东西设计的的确蛮精妙的
/**
* 梳理一下昂
* 你看看一开始
* effect 开始执行了对吧
* 里面有get set 方法对吧
* 注意这个时候effect没有执行完囊昂 在执行栈中
* 执行到了get又调用了effect.ts的track方法,这个时候是不是就拿到了什么对象的什么方法在effect执行了
* 可以轻松加愉快的拿到当前正在执行的effect
* 好回到了effect包里面如果设置个全局变量或者其他的来存一下当前对象和属性
* track继续执行的时候是不是就知道谁调用的effect了? 因为已经通知track收集全局的了。
* 这是猜想继续看后面
* 这里继续昂回到正经实际的实现
* 创建一个描述操作符号的 opterators.ts
* export const enum TrackOpter {GET」
* package/reactivity/src/core/opterators.ts
* track(target,TrackOpter.GET,key) // 对target做取值的时候用key属性。
* track(
* target,
* TrackOpter.GET,
* key
*);
* // 回到effect.ts
*/
}
}
const Setter = (isShallow:boolean = false) => {
// 当数据更新的时候就需要用到刚才上面的 getTargetMap[effect.ts中的] 通知下去更新effect
}
effect.ts
// 当前的effect当前正在执行的effect,每次执行的时候都保存当前正在执行的effect 当track调用的时候需要用到它
let activeEffect:any = null;
// 辅助当前的effect为了避免下面描述的那个大bug因为存在state和effect可能穿插着用会造成一些列的问题,下面说明一下effect嵌套是个栈所以用栈来保存当前执行的effect比较合适
let activeEffectStrack:any = [];
// 收集effect+key关联的map 收集对象key引用的effect
const getTargetMap = new WeakMap();
/**
* 收集effect
* 让某个对象中的属性手机他对应的effect函数
*/
function track(
target:any,
opter:any,
key:string
){
console.log("收集effect");
// 丁陆超猜想
// 如何让
// target key找到是哪个effect 就是我在basehander猜想的那样
// 来看看实际的实现是啥样子的?
// 第二种猜想把,effect至顶部在这里做捆绑.
// vue2就用的我说这种第二种方式
// 怎么找到 effect ?
// 来吧暴露全局变量吧
// activeEffect
console.log("最终的结果", activeEffect, target, opter, key);
console.log("最终的流程图",
'第一步定义返回的effect函数',
'第二步执行fn',
'第三步fn执行了get',
'第四步走到了收集effect,这个调用的effect库',
'第五步track可以直接拿到暴露出来的当前的effect',
'第六步直接做关联'
)
// 按照表面上看用 activeEffect 定义当前指向了哪一个effect足矣
// 其实是存在问题的
/**
* 先来看一个大坑
* effect(() => { // effect1 入栈 activeEffectStrack[effect1]
* state.name
* effect(()=>{ // effect2 入栈 activeEffectStrack[effect1, effect2]
* state.sex
* effect(()=>{ effect3 入栈 activeEffectStrack[effect1, effect2, effect3]
* state.ak47
* }) // 出栈 activeEffectStrack[effect1, effect2]
* }) // 出栈 activeEffectStrack[effect1]
* state.name1 在没有 activeEffectStrack的时候 这里就凉了全局用的是 // effect 3
*}) // 出栈 activeEffectStrack[effect1]
*
* 解决方案用一个栈记录他们的关系
* 永远来找数组的最后一个
*
* 设计的很巧妙应为调用时栈所以用栈来控制最后选择谁。
*
*/
/**
* 第二个大bug
* effect(()=>{
* state.flg++; // 死循环 // 如果 state.flg 一变就让effect执行就回进入到死循环中去。
* });
*
* 解决方案如果当前栈里面有当前正在运行的effect就别往里放了防止同样的effect一直进入死循环
*
*/
// 开始进行第六步做关联
// 处理了上面的大BUG就可以处理这里的内容了。
// Vue3对编译做了非常多的优化。
console.log(
"当前对象",
target,
key,
activeEffect
);
// key 要和 effect关联
// 数据结构
/**
* 伪代码
*
* let struct = new WeakMap(target => new WeakMap(name=>new Set([effect])))
*/
// // 如果 activeEffect 为null说明当前 target key 没有在effect中使用也就不需要去收集了,如果在effect中是使用了一定会有effect
if(activeEffect === null) return; // 当前的effect都结束了 或者effect没有用到响应数据。
// 维护 getTargetMap
// 不管里面有没有先拿出来
// 第一步先拿target对应的map
// // 如果没有target的key就创建他并且把deepMap 指向新的数据空间
let deepMap = getTargetMap.get(target);
if(!deepMap) {
// 如果没有那就创建它一个
getTargetMap.set(target, (deepMap = new Map()))
}
// 第二步拿到key对应的set
let deepKeyMap = getTargetMap.get(key);
// 如果没有就创建它
if(!deepKeyMap) {
deepMap.set(key, (deepKeyMap=new Set()))
}
// 第三步就拿到了set
// 防止出现 state.a state.a 这种无限的往里面加有问题
if(!deepKeyMap.has(activeEffect)) {
deepKeyMap.add(activeEffect);
}
// 为什么要用set?
/**
* effect(() => {
* state.name;state.name;state.name 就会把相同的effect能了三次
* })
*
* 要区分
* effect(()=>{
* state.name;
* })
*
* effect(()=>{
* state.name;
* });
* 这个确实是要收集两边的而且用到set[]去重复了还能多次执行
*/
// 这里的 deepMap 就到了map空间了
// 收集后的鬼东西
console.log("收集后的鬼东西::", getTargetMap);
// 这里完事儿了就需要走到basehander的setter了
// 当数据执行的时候通知所有的监控点就是我们这里的钩子
}
createProxyEffect
/**
*
* @param fn
* @returns
*/
// 每一个effect的唯一ID这个后面在说搞啥用的
let uid = 0;
function createProxyEffect(fn:() => {}, options:EffectOptions) {
// 每创建一个effect都需要有个唯一的身份-uid
// 源代码里面需要用到这个做排序先创建的先来执行一下。
// 用于组建的更新。
effect.id = uid++;
// effect 还需要有一个标识自己身份的状态,标识我是一个effect函数标识我是我 标识我是响应式的effect _表示私有的外界是不能获取的。
effect.isEffiect = true;
// 记录一下effect原本的函数是谁其实就是fn啦
effect.raw = fn;
// 另外把effect的配置也保存起来
effect.options = options;
/**
* 返沪的effect 需要有一个唯一的标识
*/
function effect() {
// 避免出现死循环 主义看上面的第二个大bug
// 第二个大bug 保证一轮只执行一次
// 这里区分的是函数地址
if(activeEffectStrack.includes(effect)) return;
try{
// 把effect捆绑到全局变量上来吧
activeEffect = effect;
// effect 的指针入栈操作
activeEffectStrack.push(effect);
// 测试第一次调用被执行了
console.log("测试第一次调用被执行了");
// 执行一次fn
// 用户返回的方法就是effect的返回方法。
return fn();
// 最难的我们依赖的数据就是fn中的数据变化可这个鬼东西怎么执行?
// fn函数执行的时候需要干点什么?这个注意在外边定义的不能协迫调用者干什么
// fn中的响应式数据在取值的时候会走get方法
/**
* effect(()=>{
console.log("自定义的fn被执行了...", state.a)
});
* 这是外边的调用者 state.a会去取值
*
* 问题是这个state.a 这个属性怎么和当前的effect进行关联?
*
* 这个取值在哪里?basehander.ts 的 getter这个核心函数去改改呗
* 注意这里要跑到basehandler函数去做操作。 23 行
* 跑到这里
* // 收集依赖 如果不是只读的需要收集起来一会更新试图,如果是只读的就不牵扯到视图的更新。
* 28-39行
*
* // 从basehander返回
* 创建收集effect track
*/
}finally{
// 上面函数有返回值所以这里用了finally 保证了无论如何都可以执行的到,
// 这个地方为什么要加这么个玩意try catch 因为如果fn包错了还能正常的把当前指针给扔出去。
// 当fn执行完了就执行出栈
activeEffectStrack.pop();
activeEffect = activeEffectStrack[activeEffectStrack.length-1];
}
}
return effect;
}
function effect(fn:()=>{}, options:EffectOptions) {
// 这里是做配置
// 这里是做响应式的
// 如何把effect变成响应式的,可以做到数据发生了改变重新执行它。
const ResProxyEffect = createProxyEffect(fn, options);
// 因为effect在执行的时候回先执行一次。
// 所以先跑一次
if(!options || !options.lazy){ // 如果不是懒加载就执行一下
ResProxyEffect();
}
return ResProxyEffect;
}
export {
effect,
track,
}