调试源代码查看reactivity是怎么实现的
git clone xxx --depth 1 拉取最新的一次提交
跑源代码的时候用的是yarn
项目用的也是采用了Monorepo
element-plus 也用了monorepo
每一个组件都是一个包可以对这一个包进行打包。
scripts/dev.js 测试环境的工具
monorepo 是组织代码的方式,和具体怎么打包没有啥关系。
如果在研究源代码的时候可以修改dev.js的reactivity这样写死就只关心这一个包不需要打包别的包。
深度研究reactivity
// 把dev.js 强行改成reactivity -> 默认是vue改成 reactivity 只对这一个模块处理
// 核心的api
/**
* reactive
* effect effect中的响应式变量改变了这个effect就会重新执行 【track】收集 【trigger】触发 内部方法
* ref
*/
// reactivity -> [看源代码可以从单元测试入手]|[package.json]/main 入手
// index.js 默认用的commonjs的规范 - node的
// 如果用的import 的语法找的是package.js module 对应的入口
// 在构建工具中使用默认用的是module
// 源代码的入口 src/index.ts // 这个玩意就是把内部功能导出来。
/**
* reactive 分析
* 1、target 判断是否存在并且是否存在IS_READYONLY如果存 如果对象存在并且是只读的直接返回。 防止这么操作 reactive(readonly(target))
* 如果对象被readonly包装会返回proxy 因为本身就是只读的不可能被修改了。用reactive这个包一层意义就不大了。
* 2、创建响应式对象
* createReactiveObject(
* // 目标 (array obj) 用的A (Set Map )用的B
* // 是不是readyonly
* // 拦截器 A new Proxy 对应的拦截器
* // 拦截器 B collection handler
* )
* createReactiveObject
1. 判断 target是不是对象
2. 如果不是对象就直接返回,传递的什么就返回什么
3. target[Reactiveflgs.RAW] // 对象被代理过会有这个RAW属性
4. 这么访问如果target被代理过会走到get里面
5. 观察 createGetter key === Reactiveflgs.RAW 说明这个属性已经被代理过了。 - baseHandler.ts
6. 如果被代理过就直接返回 target
7. if(target[Reactiveflgs.RAW]
&&
// 如果被readonly代理了并且被reactive没有同时代理
!(isReadonly && target[Reactiveflgs.IS_REACTIVE])){ 所以这里可以用 reactive(readonly(target)) 是可以创建target的 这种情况是ok的
return target;
}
8. const M = isReadonly ? readonlyMap : reactiveMap // 如果是只读的会产生只读的映射表,否则会产生reactive的映射表。 readonly放一起 reactive的放一起
9. 这两个是做缓存的如果创建过就直接返沪了用weakMap
10. M.get(target) 先看一下有没有代理过 // 保证对象不能被重复的代理。
11。reactive(reactive(target)) // 这是不允许的
12. 如果对象没有被代理过,看一看白名单 对象能不能去被代理 __v_skip
13. getTargetType 获取目标的类型。
14. 如果对象有个__v_skip 说明这个对象不能被代理 或者 这个对象是不可以被扩展的 !Object.isExtensible(value);
15. 如果可以被代理 toRawType(target) Object.prototype.toString.call({}).slice(8, -1) 其实就是获取类型的字符串 toRawType 这个玩意就是在获取类型。
16. targetTypeMap(toRawType(target)) 取出来target是一个普通对象,还是一个集合类型。
17. 如果返回了TargetType.INVALID 就直接返回了
18. 如果没有问题提就new Proxy
19. const Proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
20. M.set(target, Proxy); // 写入缓存
21. return Proxy
=======get=======
22. 数组的处理
const targetIsArray = isArray(target);
23. arrayInstrumentations 重写 includes indexOf lastIndexOf 类似vue2的方法劫持 并且调用原有方法 可以增加自己的逻辑
24. 为什么需要对他们三个特殊处理? ['a', 'b', 'c'].includes(dyn); dyn是个变量多种可能 因为 dyn可能是 'a'|'b'|'c' 所以需要对 a b c 进行依赖收集。
相当于 proxy(['a','b','c']).includes(dyn) // 数组的每一项都进行以来收集
25. ['push', 'pop', 'shift', 'unshift', 'splice'] 也重写了
27. pauseTracking() // 暂停跟踪?可以控制是否依赖收集 resetTracking() // 重制跟踪 这一坨有啥用? 防止length被修改了如果length被修改在某些场景会无限循环。解决的bug是2137
bug内容
const arr = reactive([])
effect(()=>{
arr.push(1)
})
effect(()=>{
arr.push(2)
})
造成的问题就是 length改变就无限执行1 2 无限的加
// 调用push 的时候回去访问数组的push length 就忽略收集了
28. 回到23继续往下走
const res = Reflect.get(target, key, receiver);
29.
isSymbol(key) ...... // 内置symbol 或者是原型链查找到的直接返回 // 这些就不考虑收集依赖了
Object.getOwnPropertyNames(Symbol) // 返回的是一些symbol的内置默认属性 或者 是 __proto__ __v_isRef, __isVue都直接返回屏蔽掉了一些内置symbol 值的获取 不需要收集symbol和链的依赖
30. if(!isReadonly){
track(target, TrackOpTypes.Get, key) // 如果不是只读就需要收集
}
31. if(shallow){
return res; 如果是浅层的就直接返回了不需要收集
}
32. if(isRef(res)){
//会对ref做特殊的处理
}
33. if(isObject(res)){
// 如果是对象就递归处理成reactive
}
34. Basehandler createSetter
if(!shallow) 如果不是浅的 可能我们这个对象被深层代理了
value = toRaw(value) 如果本身是proxy 就把它拆包了
相当于
p = reactive({r:1})
p.r = reactive({a:1}) // 给值有做了个reactive
这时候就需要把 reactive({a:1})变成 {a:1} 不需要proxy套proxy
let proxy1 = reacitve({name:1, age:ref(11)})
proxy1.name = reactive({str:''}) // 这里就拆包了 不担心多次设置proxy
proxy1.age = 11; // 这个相当于改的是老的ref(11)的value
*/
针对上面的特性进行测试
let obj = {a:1};
let a = readonly(obj);
let b = reactive(a);
a === b; // true;
// demo2
let a = reactive({});
let b = reactive(a);
let c = reactive(b);
a == b == c;
// demo3
let obj = {a:1};
let proxy = reactive(obj);
obj == toRaw(proxy); // toRaw把proxy对象变回到普通对象 true toRaw(observed){return (observed && toRaw(observed["__v_raw"])) || observed} // 取值就走get了observed["__v_raw"] observed["__v_raw"] 这个走get就从缓存里面把对象对应的target拿出来
let obj2 = {a:1};
let proxy2 = reactive(markRaw(obj2)); // markRaw 标记obj2是一个raw对象。他会给这个对象加一个"__v_skip"属性表示这个对象不可以被代理,就不能用new proxy做处理了 define object.defineProperty定义一下 proxy2 还是 obj2 有些写好的库 并不支持 proxy 需要用markRaw包装一下不让proxy代理
let a = reactive({a:markRaw({b:1})});// b就不被代理了
// demo4
// 对数组的特定处理
let proxyArray = reactive([1,2,3,4]);
// 访问数组长度的时候
proxyArray.push(1) // 调用数组长度的时候有暂停收集功能和增加收集功能。它会访问length 自动访问 它会来两次第一次push 进来第二次length进来 你取length就暂停依赖收集 track里面有个shouldTrack表示要不要收集
proxyArray[Symbol.hasInstance] // 访问内部自定义属性不会依赖收集
// 只要用到长度的都会触发 length 所以对 includes indexOf lastIndexOf 'push', 'pop', 'shift', 'unshift', 'splice' 做了单独的处理
effect(() => {
arr[0]xxx,arr.length//操作了arr属性才会需要收集和触发 一定要是取arr的动作
arr.push()// 这个并没用到具体的成员也就不需要收集依赖。
})
// 考虑了ref的情况
let r = reactive({name:ref('zf')})
// 属性有可能是被ref包裹的
// 正常情况下r.name.value是不是有点2
// 它会帮着你拆包
// 走到了 上面 32
if(isRef(xxx)) {
// 通过 __v_isRef来查看
// 如果不是数组就直接返回res.value了把包拆了 所以 r.name = r.name.value
// 所以reactive包了ref可以直接不用了value了
}
// demo5
let r1 = reactive([ref(1),2,3,4]);
r1[0].value // r1[0] != r1[0].value 这种情况是不能被拆包的 返回就是个ref没有拆包动作