经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » React » 查看文章
源码分析Vue3响应式核心之reactive
来源:jb51  时间:2023/4/24 8:51:29  对本文有异议

vue3响应式核心文章汇总:

vue3响应式核心之reactive源码详解

vue3响应式核心之effect源码详解

vue3响应式核心分两篇文章讲解,本篇讲解reactive源码和实现原理,下一篇vue3响应式核心之effect源码详解讲解effect依赖收集与触发。

一、Reactive源码

1、reactive

源码路径:packages/reactivity/src/reactive.ts

  1. export function reactive(target: object) {
  2. // if trying to observe a readonly proxy, return the readonly version.
  3. // 是否是只读响应式对象
  4. if (isReadonly(target)) {
  5. return target
  6. }
  7. return createReactiveObject(
  8. target,
  9. false,
  10. mutableHandlers,
  11. mutableCollectionHandlers,
  12. reactiveMap
  13. )
  14. }

当我们执行reactive({})的时候,会执行createReactiveObject这个工厂方法,返回一个响应式对象。

2、接着看工厂方法createReactiveObject

源码路径:packages/reactivity/src/reactive.ts

  1. function createReactiveObject(
  2. target: Target,
  3. isReadonly: boolean,
  4. baseHandlers: ProxyHandler<any>,
  5. collectionHandlers: ProxyHandler<any>,
  6. proxyMap: WeakMap<Target, any>
  7. ) {
  8. // 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
  9. if (!isObject(target)) {
  10. if (__DEV__) {
  11. console.warn(`value cannot be made reactive: ${String(target)}`)
  12. }
  13. return target
  14. }
  15. // target is already a Proxy, return it.
  16. // exception: calling readonly() on a reactive object
  17. if (
  18. target[ReactiveFlags.RAW] &&
  19. !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  20. ) {
  21. return target
  22. }
  23. // target already has corresponding Proxy
  24. const existingProxy = proxyMap.get(target)
  25. if (existingProxy) {
  26. return existingProxy
  27. }
  28. // only specific value types can be observed.
  29. const targetType = getTargetType(target)
  30. if (targetType === TargetType.INVALID) {
  31. return target
  32. }
  33. const proxy = new Proxy(
  34. target,
  35. targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  36. )
  37. proxyMap.set(target, proxy)
  38. return proxy
  39. }

仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。

  1. if (!isObject(target)) {
  2. if (__DEV__) {
  3. console.warn(`value cannot be made reactive: ${String(target)}`)
  4. }
  5. return target
  6. }

如果 target 已经是一个代理对象了,那么直接返回 target

  1. if (
  2. target[ReactiveFlags.RAW] &&
  3. !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  4. ) {
  5. return target
  6. }

如果 target 已经有对应的代理对象了,那么直接返回代理对象

  1. const existingProxy = proxyMap.get(target) // 存储响应式对象
  2. if (existingProxy) {
  3. return existingProxy
  4. }

对于不能被观察的类型,直接返回 target

  1. const targetType = getTargetType(target)
  2. if (targetType === TargetType.INVALID) {
  3. return target
  4. }
  1. // getTargetType源码
  2. function getTargetType(value: Target) {
  3. return value[ReactiveFlags.SKIP] || !Object.isExtensible(value) // 不可扩展
  4. ? TargetType.INVALID
  5. : targetTypeMap(toRawType(value))
  6. }
  7.  
  8. // ReactiveFlags枚举
  9. export const enum ReactiveFlags {
  10. // 用于标识一个对象是否不可被转为代理对象,对应的值是 __v_skip
  11. SKIP = '__v_skip',
  12. // 用于标识一个对象是否是响应式的代理,对应的值是 __v_isReactive
  13. IS_REACTIVE = '__v_isReactive',
  14. // 用于标识一个对象是否是只读的代理,对应的值是 __v_isReadonly
  15. IS_READONLY = '__v_isReadonly',
  16. // 用于标识一个对象是否是浅层代理,对应的值是 __v_isShallow
  17. IS_SHALLOW = '__v_isShallow',
  18. // 用于保存原始对象的 key,对应的值是 __v_raw
  19. RAW = '__v_raw'
  20. }
  21.  
  22. // targetTypeMap
  23. function targetTypeMap(rawType: string) {
  24. switch (rawType) {
  25. case 'Object':
  26. case 'Array':
  27. return TargetType.COMMON
  28. case 'Map':
  29. case 'Set':
  30. case 'WeakMap':
  31. case 'WeakSet':
  32. return TargetType.COLLECTION
  33. default:
  34. return TargetType.INVALID
  35. }
  36. }
  37.  
  38. // toRawType
  39. export const toRawType = (value: unknown): string => {
  40. // extract "RawType" from strings like "[object RawType]"
  41. return toTypeString(value).slice(8, -1)
  42. }

创建响应式对象(核心代码)

  1. const proxy = new Proxy(
  2. target,
  3. targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  4. )

接下来将重点讲解baseHandlers这个回调函数。

二、baseHandlers

1、baseHandlers

baseHandlersmutableHandlers, 来自于 baseHandlers文件。

mutableHandlers的源码如下,分别对get、set、deleteProperty、has、ownKeys做了代理。

  1. export const mutableHandlers: ProxyHandler<object> = {
  2. get,
  3. set,
  4. deleteProperty,
  5. has,
  6. ownKeys
  7. }

接下来看看这些个拦截器的具体实现。

(1)、get的代理

  1. const get = /*#__PURE__*/ createGetter()
  2.  
  3. function createGetter(isReadonly = false, shallow = false) {
  4. // 闭包返回 get 拦截器方法
  5. return function get(target: Target, key: string | symbol, receiver: object) {
  6. // 如果访问的是 __v_isReactive 属性,那么返回 isReadonly 的取反值
  7. if (key === ReactiveFlags.IS_REACTIVE) {
  8. return !isReadonly
  9. // 如果访问的是 __v_isReadonly 属性,那么返回 isReadonly 的值
  10. } else if (key === ReactiveFlags.IS_READONLY) {
  11. return isReadonly
  12. // 如果访问的是 __v_isShallow 属性,那么返回 shallow 的值
  13. } else if (key === ReactiveFlags.IS_SHALLOW) {
  14. return shallow
  15. // 如果访问的是 __v_raw 属性,那么返回 target
  16. } else if (
  17. key === ReactiveFlags.RAW &&
  18. receiver ===
  19. (isReadonly
  20. ? shallow
  21. ? shallowReadonlyMap
  22. : readonlyMap
  23. : shallow
  24. ? shallowReactiveMap
  25. : reactiveMap
  26. ).get(target)
  27. ) {
  28. return target
  29. }
  30.  
  31. // target是否是数组
  32. const targetIsArray = isArray(target)
  33.  
  34. if (!isReadonly) { // 可读
  35. // 如果是数组,并且访问的是数组的一些方法,那么返回对应的方法
  36. /**
  37. * Vue3中使用 arrayInstrumentations对数组的部分方法做了处理,为什么要这么做呢?
  38. * 对于 push、pop、 shift、 unshift、 splice 这些方法,
  39. * 写入和删除时底层会获取当前数组的length属性,如果我们在effect中使用的话,
  40. * 会收集length属性的依赖,当使用这些api是也会更改length,就会造成死循环:
  41. * */
  42. if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
  43. // 返回重写的push、pop、 shift、 unshift、 splice 'includes', 'indexOf', 'lastIndexOf'
  44. return Reflect.get(arrayInstrumentations, key, receiver)
  45. }
  46. // 如果访问的是 hasOwnProperty 方法,那么返回 hasOwnProperty 方法
  47. if (key === 'hasOwnProperty') {
  48. return hasOwnProperty
  49. }
  50. }
  51.  
  52. // 获取 target 的 key 属性值
  53. const res = Reflect.get(target, key, receiver)
  54.  
  55. // 如果是内置的 Symbol,或者是不可追踪的 key,那么直接返回 res
  56. if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
  57. return res
  58. }
  59.  
  60. // 如果不是只读的,那么进行依赖收集
  61. if (!isReadonly) {
  62. track(target, TrackOpTypes.GET, key)
  63. }
  64.  
  65. // 如果是浅的,那么直接返回 res
  66. if (shallow) {
  67. return res
  68. }
  69. // 如果 res 是 ref,对返回的值进行解包
  70. if (isRef(res)) {
  71. // ref unwrapping - skip unwrap for Array + integer key.
  72. return targetIsArray && isIntegerKey(key) ? res : res.value
  73. }
  74. // 如果 res 是对象,递归代理
  75. if (isObject(res)) {
  76. // Convert returned value into a proxy as well. we do the isObject check
  77. // here to avoid invalid value warning. Also need to lazy access readonly
  78. // and reactive here to avoid circular dependency.
  79. return isReadonly ? readonly(res) : reactive(res)
  80. }
  81.  
  82. return res
  83. }
  84. }

当target是数组的时候,'push', 'pop', 'shift', 'unshift', 'splice'这些方法会改变数组长度,会导致无限递归,因此要先暂停收集依赖, 所以对数组的以上方法进行了拦截和重写

  1. if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
  2. // 返回重写的push、pop、 shift、 unshift、 splice 'includes', 'indexOf', 'lastIndexOf'
  3. return Reflect.get(arrayInstrumentations, key, receiver)
  4. }

重写的代码:

  1. const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()
  2.  
  3. function createArrayInstrumentations() {
  4. const instrumentations: Record<string, Function> = {}
  5. // instrument length-altering mutation methods to avoid length being tracked
  6. // which leads to infinite loops in some cases (#2137)
  7. ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
  8. instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
  9. // 由于上面的方法会改变数组长度,因此暂停收集依赖,不然会导致无限递归
  10. console.log('----自定义push等入口:this, args, key');
  11. pauseTracking()
  12. console.log('----自定义push等暂停收集依赖&执行开始')
  13. // 调用原始方法
  14. const res = (toRaw(this) as any)[key].apply(this, args)
  15. console.log('----自定义push等暂停收集依赖&执行结束')
  16. //复原依赖收集
  17. resetTracking()
  18. return res
  19. }
  20. })
  21. return instrumentations
  22. }

下图是执行结果:

可以用以下代码来理解:

  1. let arr = [1,2,3]
  2. let obj = {
  3. 'push': function(...args) {
  4. // 暂停收集依赖逻辑
  5. return Array.prototype.push.apply(this, [...args])
  6. // 启动收集依赖逻辑
  7. }
  8. }
  9. let proxy = new Proxy(arr, {
  10. get: function (target, key, receiver) {
  11. console.log('get的key为 ===>' + key);
  12. let res = '';
  13. if(key === 'push') { //重写push
  14. res = Reflect.get(obj, key, receiver)
  15. } else {
  16. res = Reflect.get(target, key, receiver)
  17. }
  18. return res
  19. },
  20. set(target, key, value, receiver){
  21. console.log('set的key为 ===>' + key, value);
  22. return Reflect.set(target, key, value, receiver);
  23. }
  24. })
  25.  
  26. proxy.push('99')

特殊属性的不进行依赖收集

  1. // 如果是内置的 Symbol,或者是不可追踪的 key,那么直接返回 res
  2. if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
  3. return res;
  4. }

这一步是为了过滤一些特殊的属性,例如原生的Symbol类型的属性,如:Symbol.iterator、Symbol.toStringTag等等,这些属性不需要进行依赖收集,因为它们是内置的,不会改变;

还有一些不可追踪的属性,如:proto、__v_isRef、__isVue这些属性也不需要进行依赖收集;

依赖收集

  1. // 如果不是只读的,那么进行依赖收集
  2. if (!isReadonly) {
  3. track(target, "get" /* TrackOpTypes.GET */, key);
  4. }

浅的不进行递归代理

  1. if (shallow) {
  2. return res;
  3. }

对返回值进行解包

  1. // 如果 res 是 ref,对返回的值进行解包
  2. if (isRef(res)) {
  3. // 对于数组和整数类型的 key,不进行解包
  4. return targetIsArray && isIntegerKey(key) ? res : res.value;
  5. }

这一步是为了处理ref的情况,如果res是ref,那么就对res进行解包,这里有一个判断,如果是数组,并且key是整数类型,那么就不进行解包;因为reactive是深层响应式的,所以要把属性为ref的进行解包

对象的递归代理

  1. // 如果 res 是对象,那么对返回的值进行递归代理
  2. if (isObject(res)) {
  3. return isReadonly ? readonly(res) : reactive(res);
  4. }

(2)、set的代理

  1. const set = /*#__PURE__*/ createSetter()
  2.  
  3. function createSetter(shallow = false) {
  4. // 返回一个set方法
  5. return function set(
  6. target: object,
  7. key: string | symbol,
  8. value: unknown,
  9. receiver: object
  10. ): boolean {
  11. let oldValue = (target as any)[key] // 获取旧值
  12. // 如果旧值是只读的,并且是 ref,并且新值不是 ref
  13. if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
  14. return false
  15. }
  16. if (!shallow) { // 非shallow
  17. // 新值非shallow && 非只读
  18. if (!isShallow(value) && !isReadonly(value)) {
  19. // 获取新旧值的原始值
  20. oldValue = toRaw(oldValue)
  21. value = toRaw(value)
  22. }
  23. // 代理对象非数组 & 旧值是ref & 新值非ref
  24. if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
  25. oldValue.value = value
  26. return true
  27. }
  28. } else {
  29. // in shallow mode, objects are set as-is regardless of reactive or not
  30. }
  31. console.log('----set', target, key, value)
  32.  
  33. // 是数组 & key是整型数字 ?
  34. // 如果 key 小于数组的长度,那么就是有这个 key :
  35. // 如果不是数组,那么就是普通对象,直接判断是否有这个 key
  36. // 数组会触发两次set: index和新增的值 和 'length'和新增之后的数组长度
  37. const hadKey =
  38. isArray(target) && isIntegerKey(key)
  39. ? Number(key) < target.length
  40. : hasOwn(target, key)
  41. // 设置key-value
  42. const result = Reflect.set(target, key, value, receiver)
  43. // don't trigger if target is something up in the prototype chain of original
  44. // 如果目标对象是原始数据的原型链中的某个元素,则不会触发依赖收集
  45. if (target === toRaw(receiver)) {
  46. if (!hadKey) {// 如果没有这个 key,那么就是新增了一个属性,触发 add 事件
  47. trigger(target, TriggerOpTypes.ADD, key, value)
  48. } else if (hasChanged(value, oldValue)) { // // 如果有这个 key,那么就是修改了一个属性,触发 set 事件
  49. trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  50. }
  51. }
  52. // 返回结果,这个结果为 boolean 类型,代表是否设置成功
  53. return result
  54. }
  55. }

主要逻辑:

获取旧值

  1. let oldValue = target[key];

判断旧值是否是只读的

  1. // 如果旧值是只读的,并且是 ref,并且新值不是 ref,那么直接返回 false,代表设置失败
  2. if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
  3. return false;
  4. }

以上就是源码分析Vue3响应式核心之reactive的详细内容,更多关于Vue3 reactive的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号