源码调试

yarn install 安装依赖

yarn dev 只打包了 packages下的 vue 文件,若要全部打包 执行 yarn build

reactvie.js

四个方法

reactive、readonly、shallowReactive、shallowReadonly

  1. import {
  2. mutableHandlers,
  3. readonlyHandlers,
  4. shallowReactiveHandlers,
  5. shallowReadonlyHandlers
  6. } from './baseHandlers'
  7. // 存储代理过的对象 WeakMap 会自动垃圾回收,不会造成内存泄漏,存储的key只能是对象
  8. export const reactiveMap = new WeakMap<Target, any>()
  9. export const shallowReactiveMap = new WeakMap<Target, any>()
  10. export const readonlyMap = new WeakMap<Target, any>()
  11. export const shallowReadonlyMap = new WeakMap<Target, any>()
  12. export function reactive(target: object) {
  13. // if trying to observe a readonly proxy, return the readonly version.
  14. if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
  15. return target
  16. }
  17. return createReactiveObject(
  18. target,
  19. false,
  20. mutableHandlers,
  21. mutableCollectionHandlers,
  22. reactiveMap
  23. )
  24. }
  25. export function shallowReactive<T extends object>(target: T): T {
  26. return createReactiveObject(
  27. target,
  28. false,
  29. shallowReactiveHandlers,
  30. shallowCollectionHandlers,
  31. shallowReactiveMap
  32. )
  33. }
  34. export function readonly<T extends object>(
  35. target: T
  36. ): DeepReadonly<UnwrapNestedRefs<T>> {
  37. return createReactiveObject(
  38. target,
  39. true,
  40. readonlyHandlers,
  41. readonlyCollectionHandlers,
  42. readonlyMap
  43. )
  44. }
  45. export function shallowReadonly<T extends object>(
  46. target: T
  47. ): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
  48. return createReactiveObject(
  49. target,
  50. true,
  51. shallowReadonlyHandlers,
  52. shallowReadonlyCollectionHandlers,
  53. shallowReadonlyMap
  54. )
  55. }

这四个方法要根据是不是仅读,是不是深度来实现的核心功能 proxy 代理,所以就通过函数柯里化的方式,让 createReactiveObject 根据不同的传参实现最核心的数据拦截。

  1. // 底层 new Proxy() 最核心的需要拦截 数据的读取和数据的修改
  2. function createReactiveObject(
  3. target: Target, // 响应式目标数据
  4. isReadonly: boolean, // 是不是仅读的
  5. baseHandlers: ProxyHandler<any>, // proxy 处理函数
  6. collectionHandlers: ProxyHandler<any>,
  7. proxyMap: WeakMap<Target, any> // 存储目标对象和代理后的对象映射关系
  8. ) {
  9. // 如果目标不是对象就没法拦截 直接返回就行
  10. if (!isObject(target)) {
  11. if (__DEV__) {
  12. console.warn(`value cannot be made reactive: ${String(target)}`)
  13. }
  14. return target
  15. }
  16. // 如果这个对象被代理过了 就不要再代理了
  17. const existingProxy = proxyMap.get(target)
  18. if (existingProxy) {
  19. return existingProxy
  20. }
  21. // 最核心的 proxy 代理
  22. const proxy = new Proxy(
  23. target,
  24. baseHandlers
  25. )
  26. // 第一次代理将目标对象 和代理后的对象存起来
  27. proxyMap.set(target, proxy)
  28. return proxy
  29. }

首先判断目标是否对象,如果不是就不用代理了,直接返回。
其次,判断这个对象是否已经被代理过了,代理过就不用再代理了。
如果没有代理就通过 newProxy 实现代理,并返回代理后的对象。
最后将目标对象和代理后的对象存起来。

baseHandlers 是 代理处理函数,接下来我们就看看具体怎么根据不同的参数进行处理的。
image.png
这四个handler 都有 get 和 set 方法,所以抽离出来。
是不是仅读的,仅读的set时会报错
是不是深度代理

  1. // TODO: 四种getter
  2. const get = /*#__PURE__*/ createGetter()
  3. const shallowGet = /*#__PURE__*/ createGetter(false, true)
  4. const readonlyGet = /*#__PURE__*/ createGetter(true)
  5. const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true) // 仅第一层不能修改,第二次层可以修改
  1. const set = /*#__PURE__*/ createSetter()
  2. const shallowSet = /*#__PURE__*/ createSetter(true)

下面是最核心的 get 和 set 的实现方法,触发 get 的时候要 收集依赖, 触发set 的时候要 更新依赖

  1. // TODO: 拦截获取功能 是不是仅读的, 是不是 浅的 proxy + Reflect
  2. // Reflect 方法具备返回值设置成功过会返回 true ,失败返回 false
  3. // 会依赖收集,等数据变化了就更新对应的视图 track
  4. function createGetter(isReadonly = false, shallow = false) {
  5. return function get(target: Target, key: string | symbol, receiver: object) { // target 原对象,key target 的属性, receiver 代理对象
  6. if (key === ReactiveFlags.IS_REACTIVE) {
  7. return !isReadonly
  8. } else if (key === ReactiveFlags.IS_READONLY) {
  9. return isReadonly
  10. } else if (
  11. key === ReactiveFlags.RAW &&
  12. receiver ===
  13. (isReadonly
  14. ? shallow
  15. ? shallowReadonlyMap
  16. : readonlyMap
  17. : shallow
  18. ? shallowReactiveMap
  19. : reactiveMap
  20. ).get(target)
  21. ) {
  22. return target
  23. }
  24. // Reflect 取值 target[key]
  25. const res = Reflect.get(target, key, receiver)
  26. // 不是仅读就收集依赖
  27. if (!isReadonly) {
  28. // 仅读的属性不变,不用收集依赖,不是仅读的才会依赖收集
  29. track(target, TrackOpTypes.GET, key) // 监控哪个对象里的那个属性 TrackOpTypes.GET 操作标识,可能还有 HAS,表示对这个对象做什么操作的时候给他收集起来
  30. }
  31. // 浅代理 取出第一层 直接返回
  32. if (shallow) {
  33. return res
  34. }
  35. // 嵌套对象 readonly为true就用 readonly包裹 ,如果不是就递归用 reactive包裹
  36. // vue2 是一上来就递归,vue3 是当取值的时候会进行代理 懒代理
  37. if (isObject(res)) {
  38. return isReadonly ? readonly(res) : reactive(res)
  39. }
  40. return res
  41. }
  42. }

vue2 中在 取值的时候会触发依赖收集,vue3也是在get中进行依赖收集。
依赖收集的前提是这个这个api不是仅读的,仅读不会发生改变就不进行依赖收集。
不是仅读就会走 依赖收集。
如果是浅代理,就从target中取出第一层并直接返回,如果是嵌套对象就再判断他是否是readOnly,是的话就用 readOnly 包裹一层,不是的话就递归调用 reactive 包裹。

接下来看看最重要的依赖收集

依赖收集

image.png
effect 是响应式包中的一个方法,默认页面刷新会执行一次,在执行这个 effect 方法的时候会 走get 方法获取响应式数据的属性值,所以可以将 effect 和 这个对象属性关联起来,当对象属性发生变化后会重新执行 effect 方法。这个属性跟vue2 的 update 方法很相似,但是不同的是 updata 任何属性发生变化都会重新渲染,而 effect 是只有依赖属性发生变化才会重新渲染。

  1. // effect 变成响应式的effect,可以做到数据变化重新执行
  2. export function effect<T = any>(
  3. fn: () => T,
  4. options: ReactiveEffectOptions = EMPTY_OBJ
  5. ): ReactiveEffect<T> {
  6. if (isEffect(fn)) {
  7. fn = fn.raw
  8. }
  9. // 例子:
  10. // effect( () => {
  11. // app.innerHTML = 'xxx',
  12. // })
  13. const effect = createReactiveEffect(fn, options)
  14. // 默认的 effect 会先执行一次 如果有lazy 就不执行
  15. if (!options.lazy) {
  16. effect() // 响应式的effect默认执行一次
  17. }
  18. return effect
  19. }
  20. // effect 标识,用于区分effect,effect 相当于 vue2 的 watcher
  21. let uid = 0
  22. // 1.函数调用栈
  23. // effect( () => { effect1
  24. // state.name,
  25. // effect(()=>{ effect2
  26. // state.age,
  27. // })
  28. // state.address, effect2
  29. // })
  30. // 2.死循环
  31. // effect(() => {
  32. // state.age ++
  33. // })
  34. function createReactiveEffect<T = any>(
  35. fn: () => T, // fn 执行时会取值,会走 get 方法,就会依赖收集
  36. options: ReactiveEffectOptions
  37. ): ReactiveEffect<T> {
  38. const effect = function reactiveEffect(): unknown {
  39. if (!effect.active) {
  40. return fn()
  41. }
  42. // 判断当前effect 是否在 栈中,避免死循环的情况
  43. if (!effectStack.includes(effect)) {
  44. cleanup(effect)
  45. try {
  46. enableTracking()
  47. effectStack.push(effect) // 执行这个effect的时候入栈
  48. activeEffect = effect
  49. return fn() // 函数执行完了
  50. } finally { // 用 finally 是希望函数执行异常了还能正确的将当前 effect pop 出去
  51. effectStack.pop() // 执行完了出战
  52. resetTracking()
  53. activeEffect = effectStack[effectStack.length - 1] // 当前的effect 永远指向栈中的最后一个effect
  54. }
  55. }
  56. } as ReactiveEffect
  57. effect.id = uid++ // 每创建一个effect 就制作一个 effect 标识
  58. effect.allowRecurse = !!options.allowRecurse
  59. effect._isEffect = true // 用于标识这个是响应式effect
  60. effect.active = true
  61. effect.raw = fn // 响应式effect 对应的原函数
  62. effect.deps = []
  63. effect.options = options // 属性
  64. return effect
  65. }

effect 标识用于组件更新,更新哪个effect,
响应式effect
effect原函数
调用 effect 的时候会执行 effect 传递过来的参数 fn(),执行这个 fn 函数的时候会走 get 方法取值,这个时候就可以将 effect 和 依赖的属性关联起来,等 依赖的属性再次发生变化了就可以再次执行 effect 函数更新视图。
在上面的 createGetter 方法中有个 track 函数就是用来收集依赖的。一会儿看这块的源码。
我们可以看到有activeEffect和effectStack,activeEffect 用于标记当前执行的是哪个 effect 函数,那为什么要整一个函数调用栈?有一种情况如下:

  1. effect( () => { effect1
  2. state.name,
  3. effect(()=>{ effect2
  4. state.age,
  5. })
  6. state.address, effect2
  7. })

当 effect2 执行完之后当前 activeEffect 还是等于 effect2,state.address 的取值就会变成 effect,这样就乱了。所以用一个函数调用栈,执行完就弹出,activeEffect 永远是 栈中的最后一个。
每次执行入栈,执行完出栈。

  1. // 让对象中的某个属性收集他对应的 effect 函数
  2. // weakMap key => {name: 'hhh', age: 12}, value(是一个map,key:name,value:[effect]) =>{name: set[effect, effect]} 一个属性取几次就会存几个effect effect(()=>{state.name}) effect(()=>{state.name})
  3. export function track(target: object, type: TrackOpTypes, key: unknown) {
  4. // 只收集在 effect 使用的属性
  5. if (!shouldTrack || activeEffect === undefined) {
  6. return
  7. }
  8. // 判断对象是否有
  9. let depsMap = targetMap.get(target)
  10. if (!depsMap) { // 没有对象的话创建映射表
  11. targetMap.set(target, (depsMap = new Map()))
  12. }
  13. // 判断对象的key是否存在
  14. let dep = depsMap.get(key)
  15. if (!dep) { //对象的key没有的话创建映射表
  16. depsMap.set(key, (dep = new Set()))
  17. }
  18. // 对象的 key的 effect 是否在存在,不存在就放进去
  19. if (!dep.has(activeEffect)) {
  20. dep.add(activeEffect)
  21. activeEffect.deps.push(dep)
  22. if (__DEV__ && activeEffect.options.onTrack) {
  23. activeEffect.options.onTrack({
  24. effect: activeEffect,
  25. target,
  26. type,
  27. key
  28. })
  29. }
  30. }
  31. }

定义一个weakmap,key 就是响应式对象,value 可以也是一个map,这个map 的key 就是 响应式对象的属性,value就是他对应的 effect 函数,因为一个 属性可能对应好几个 effect 函数,所以 这个value 是一个 Set,set 的好处是可以去重。
{
{name: ‘hhh’, age: 12}: {name:set[effect, effect]}
}
activeEffect 是当前执行的effect,vue3 和 vue2 都用一个全局变量标识当前effect。
只收集在effect 中用到的属性,没用到的就不收集。
用 set 存放 effect 的好处是可以去重,例如:

  1. effect(()=>{
  2. state.name
  3. })
  4. effect(()=>{
  5. state.name
  6. })

这种情况 同一个属性就会收集两次effect。使用set 可以去重。
接下来就是当数据更新时通知对应属性的 effect 执行。

触发更新

vue2 无法监控更改数组索引,无法监控数组长度变化。
更新值 会走 set ,更新包括新增和修改。
对象区分新增属性和修改属性的方法就是看老值有没有,有就是 修改,没有就是新增。
数组区分新增和修改:
修改索引就看这个索引和length哪个大,如果索引大于length就是新增,如果索引小于length就是修改,判断索引的方法就是看这个 key 是否是字符串类型的数字。
// 判断是否是一个数字类型key
exportconstisIntegerKey = (key: unknown) =>
isString(key) &&
‘’ + parseInt(key, 10) === key
parseInt
parseInt转数字成整型,+ ‘’成字符串,如果不是字符串数字,parseInt 转完之后就是NaN,+ ‘’,之后和原来的不一样,就证明不是索引 。
新增 调用 trigger(), 触发类型 ADD,对应的key,value,修改触发类型是 SET,对应的key,新值,老值。

  1. // TODO: 拦截设置功能 当数据更新时 通知对应属性的 effect 重新执行
  2. function createSetter(shallow = false) {
  3. // 区分新增的还是修改的 vue2 里无法监控更改索引,无法监控数组长度变化
  4. return function set(
  5. target: object,
  6. key: string | symbol,
  7. value: unknown,
  8. receiver: object
  9. ): boolean {
  10. let oldValue = (target as any)[key] // 获取老值 如果老值存在就是修改,如果老值不存在就是新增
  11. if (!shallow) {
  12. value = toRaw(value)
  13. oldValue = toRaw(oldValue)
  14. if (!isArray(target) && isRef(oldValue) && !isRef(value)) { // 如果是数组
  15. oldValue.value = value
  16. return true
  17. }
  18. } else {
  19. // in shallow mode, objects are set as-is regardless of reactive or not
  20. }
  21. const hadKey =
  22. isArray(target) && isIntegerKey(key)
  23. ? Number(key) < target.length // 小于长度就是有,修改,大于长度就是新增的
  24. : hasOwn(target, key) // 其他情况就是对象了,判断这个对象中是否有这个属性,有就是修改
  25. const result = Reflect.set(target, key, value, receiver)
  26. // don't trigger if target is something up in the prototype chain of original
  27. if (target === toRaw(receiver)) {
  28. if (!hadKey) { // 不存在就是新增
  29. trigger(target, TriggerOpTypes.ADD, key, value) // 触发更新
  30. } else if (hasChanged(value, oldValue)) { // 存在且新旧值不一样就是修改
  31. trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  32. }
  33. }
  34. return result
  35. }
  36. }

image.png

image.png

触发更新的本质是让对应的 effect 函数执行,如果这个属性没有收集依赖,就不需要做任何事,直接返回即可。
找到这个属性对应的所有 effect 函数,存到一个新的集合中,然后一起触发更新。

修改数组的长度可能也会涉及到 索引的修改,如下:

  1. effect(() =>{
  2. return state.arr[2]
  3. })
  4. setTimeout(() =>{
  5. state.arr.length = 1;
  6. })
  1. // 触发更新
  2. export function trigger(
  3. target: object,
  4. type: TriggerOpTypes,
  5. key?: unknown,
  6. newValue?: unknown,
  7. oldValue?: unknown,
  8. oldTarget?: Map<unknown, unknown> | Set<unknown>
  9. ) {
  10. const depsMap = targetMap.get(target)
  11. // 看看这个属性有没有被收集过 effect,没收集过就不需要做任何操作
  12. if (!depsMap) {
  13. // never been tracked
  14. return
  15. }
  16. // 将所有要执行的effect全部存在到一个新的集合中,最终一起执行
  17. const effects = new Set<ReactiveEffect>() // 这里对 effect 去重 effect(()=>{dom.innerHTML = state.arr[2] + state.arr.length}) 这种情况会收集一次effect
  18. const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
  19. if (effectsToAdd) {
  20. effectsToAdd.forEach(effect => {
  21. if (effect !== activeEffect || effect.allowRecurse) {
  22. effects.add(effect)
  23. }
  24. })
  25. }
  26. }
  27. // 这种情况会执行两次 effect
  28. // setTimeout(() => {
  29. // state.arr.length = 1;
  30. // state.arr.length = 3;
  31. // })
  32. if (type === TriggerOpTypes.CLEAR) {
  33. // collection being cleared
  34. // trigger all effects for target
  35. depsMap.forEach(add)
  36. } else if (key === 'length' && (target)) { // 1. 看修改的是不是数组长度 改长度影响比较大
  37. depsMap.forEach((dep, key) => {
  38. // 如果更改的长度小于收集的索引,那么这个索引也需要触发 effect 更新
  39. if (key === 'length' || key >= (newValue as number)) { // state.arr[2] state.arr.length = 1 2 >1
  40. add(dep)
  41. }
  42. })
  43. } else { // 改的不是数组了 可能是对象
  44. // schedule runs for SET | ADD | DELETE
  45. if (key !== void 0) { // 修改对象 新增不会走这儿
  46. add(depsMap.get(key))
  47. }
  48. // 执行收集的 effect
  49. // effects.forEach((effect) => effect())
  50. // also run for iteration key on ADD | DELETE | Map.SET
  51. switch (type) {
  52. case TriggerOpTypes.ADD: // 如果修改的是数组的索引 state.arr[100] 索引大于数组的长度 那么就是新增 传过来的 type 就是add
  53. if (!isArray(target)) {
  54. add(depsMap.get(ITERATE_KEY))
  55. if (isMap(target)) {
  56. add(depsMap.get(MAP_KEY_ITERATE_KEY))
  57. }
  58. } else if (isIntegerKey(key)) {
  59. // new index added to array -> length changes
  60. add(depsMap.get('length'))
  61. }
  62. break
  63. case TriggerOpTypes.DELETE:
  64. if (!isArray(target)) {
  65. add(depsMap.get(ITERATE_KEY))
  66. if (isMap(target)) {
  67. add(depsMap.get(MAP_KEY_ITERATE_KEY))
  68. }
  69. }
  70. break
  71. case TriggerOpTypes.SET:
  72. if (isMap(target)) {
  73. add(depsMap.get(ITERATE_KEY))
  74. }
  75. break
  76. }
  77. }
  78. const run = (effect: ReactiveEffect) => {
  79. if (__DEV__ && effect.options.onTrigger) {
  80. effect.options.onTrigger({
  81. effect,
  82. target,
  83. key,
  84. type,
  85. newValue,
  86. oldValue,
  87. oldTarget
  88. })
  89. }
  90. if (effect.options.scheduler) {
  91. effect.options.scheduler(effect)
  92. } else {
  93. effect()
  94. }
  95. }
  96. effects.forEach(run)
  97. }

流程图

image.png

image.png