• Vue3 源码阅读(7):响应式系统 —— 响应式 API 精讲


    这篇文章详细讲讲 Vue3 中的响应式 API,如下图所示:

    1,响应式:核心

    1-1,ref

    ref 的源码解析点击这里

    1-2,computed

    computed 的源码解析点击这里

    1-3,reactive

    源码如下所示,代码解释在注释中。

    1. export function reactive(target: object) {
    2. // 如果当前的 target 参数是只读数据的话,直接 return target。
    3. // 只读数据无需转换成响应式的,因为只读数据不会变更,进而也就不会触发依赖执行,响应式的特征没有意义
    4. if (isReadonly(target)) {
    5. return target
    6. }
    7. // 调用 createReactiveObject 函数完成响应式数据的转换
    8. return createReactiveObject(
    9. // 转换的目标对象
    10. target,
    11. // 是否是只读的
    12. false,
    13. // 用于处理 Object 和 Array 数据类型的 Proxy handler
    14. mutableHandlers,
    15. // 用于处理 Map、Set、WeakMap、WeakSet 数据类型的 Proxy handler
    16. mutableCollectionHandlers,
    17. // 一个 Map 数据,键是转换成响应式的原生对象,值是其对应的响应式对象。
    18. // 这个数据起到一个响应式对象缓存的作用
    19. reactiveMap
    20. )
    21. }
    1. function createReactiveObject(
    2. target: Target,
    3. isReadonly: boolean,
    4. baseHandlers: ProxyHandler<any>,
    5. collectionHandlers: ProxyHandler<any>,
    6. proxyMap: WeakMapany>
    7. ) {
    8. // 判断 target 是不是对象类型,如果不是的话,直接 return target
    9. if (!isObject(target)) {
    10. if (__DEV__) {
    11. console.warn(`value cannot be made reactive: ${String(target)}`)
    12. }
    13. return target
    14. }
    15. // 如果 target 已经是一个代理数据的话,直接返回它。
    16. // 例外情况:readonly 类型的数据也会被当做响应式对象,直接返回。
    17. if (
    18. target[ReactiveFlags.RAW] &&
    19. !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
    20. ) {
    21. return target
    22. }
    23. // 查看当前的 target 是不是已经被转化成响应式数据了,如果已经被转化成响应式数据的话,
    24. // 则直接返回缓存中的对应代理对象。
    25. const existingProxy = proxyMap.get(target)
    26. if (existingProxy) {
    27. return existingProxy
    28. }
    29. // 获取当前 target 的数据类型,数据类型有三大类,第一类是常规代理数据,如:Object、Array
    30. // 第二大类是收集类,如:Map、Set、WeakMap、WeakSet
    31. // 第三大类是非法类,也就是说除了上面 6 种数据类型,其他数据都无法转换成响应式的。
    32. const targetType = getTargetType(target)
    33. // 如果当前的数据类型是 INVALID 的话,直接返回 target
    34. if (targetType === TargetType.INVALID) {
    35. return target
    36. }
    37. // 创建 Proxy 对象,并且根据 target 数据类型使用不同的 handler
    38. const proxy = new Proxy(
    39. target,
    40. targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
    41. )
    42. // 将当前转换的响应式数据缓存到 proxyMap 中
    43. proxyMap.set(target, proxy)
    44. // 返回代理响应式数据
    45. return proxy
    46. }
    47. const enum TargetType {
    48. INVALID = 0,
    49. COMMON = 1,
    50. COLLECTION = 2
    51. }
    52. function targetTypeMap(rawType: string) {
    53. switch (rawType) {
    54. case 'Object':
    55. case 'Array':
    56. return TargetType.COMMON
    57. case 'Map':
    58. case 'Set':
    59. case 'WeakMap':
    60. case 'WeakSet':
    61. return TargetType.COLLECTION
    62. default:
    63. return TargetType.INVALID
    64. }
    65. }

    baseHandlers 和 collectionHandlers handler 如下所示:

    1. // baseHandlers
    2. export const mutableHandlers: ProxyHandler<object> = {
    3. get,
    4. set,
    5. deleteProperty,
    6. has,
    7. ownKeys
    8. }
    9. // 代理收集类型的数据和 object、Array 是不一样的,收集类型的数据有专门的属性和方法进行数据的读写,
    10. // 因此实现收集类数据响应式的关键是:重写收集类数据上的方法,重写的方法是使用 Proxy handler 中的 get,
    11. // 当我们执行 map.set(xx, xx) 函数时,其实内部执行了两个操作,第一个操作是获取 map 中的 set 方法,
    12. // 第二步才是执行这个方法,所以我们可以通过 get handler 自定义方法的获取逻辑,获取我们的重写方法。
    13. // 当代理的对象是 Map、Set、WeakMap 和 WeakSet 时,使用如下的 handler
    14. export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
    15. get: /*#__PURE__*/ createInstrumentationGetter(false, false)
    16. }

    createInstrumentationGetter 函数的内容如下所示:

    1. const [
    2. mutableInstrumentations,
    3. readonlyInstrumentations,
    4. shallowInstrumentations,
    5. shallowReadonlyInstrumentations
    6. ] = /* #__PURE__*/ createInstrumentations()
    7. // 创建收集类数据的 Proxy get handler
    8. function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
    9. // instrumentations 是一个对象,这个对象的 key 是收集类数据的方法名,value 是对应的重写方法
    10. const instrumentations = shallow
    11. ? isReadonly
    12. ? shallowReadonlyInstrumentations
    13. : shallowInstrumentations
    14. : isReadonly
    15. ? readonlyInstrumentations
    16. : mutableInstrumentations
    17. // 收集类数据的代理 get handler,借助这个重写收集类数据的方法
    18. return (
    19. target: CollectionTypes,
    20. key: string | symbol,
    21. receiver: CollectionTypes
    22. ) => {
    23. if (key === ReactiveFlags.IS_REACTIVE) {
    24. return !isReadonly
    25. } else if (key === ReactiveFlags.IS_READONLY) {
    26. return isReadonly
    27. } else if (key === ReactiveFlags.RAW) {
    28. return target
    29. }
    30. // 如果 key 是需要重写的方法名时,返回 instrumentations[key](返回重写的方法)
    31. return Reflect.get(
    32. hasOwn(instrumentations, key) && key in target
    33. ? instrumentations
    34. : target,
    35. key,
    36. receiver
    37. )
    38. }
    39. }

    1-4,readonly

    readonly 的源码如下所示:

    1. export function readonlyextends object>(
    2. target: T
    3. ): DeepReadonly<UnwrapNestedRefs> {
    4. return createReactiveObject(
    5. target,
    6. true,
    7. readonlyHandlers,
    8. readonlyCollectionHandlers,
    9. readonlyMap
    10. )
    11. }

    实现功能的重点在 readonlyHandlers 和 readonlyCollectionHandlers 这两个只读专属的 Proxy handlers。

    readonlyHandlers 的内容如下所示:

    1. export const readonlyHandlers: ProxyHandler<object> = {
    2. get: readonlyGet,
    3. set(target, key) {
    4. if (__DEV__) {
    5. warn(
    6. `Set operation on key "${String(key)}" failed: target is readonly.`,
    7. target
    8. )
    9. }
    10. return true
    11. },
    12. deleteProperty(target, key) {
    13. if (__DEV__) {
    14. warn(
    15. `Delete operation on key "${String(key)}" failed: target is readonly.`,
    16. target
    17. )
    18. }
    19. return true
    20. }
    21. }

    可以发现,实现很简单,当我们想变更数据的时候,打印出只读的警告,并且不做任何变更操作,直接 return true。

    readonlyCollectionHandlers 的内容如下所示:

    1. export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
    2. get: /*#__PURE__*/ createInstrumentationGetter(true, false)
    3. }

    可以发现,还是调用 createInstrumentationGetter 函数,只不过第一个参数 isReadonly 为 true。

    1. // 创建收集类数据的 Proxy get handler
    2. function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
    3. // instrumentations 是一个对象,这个对象的 key 是收集类数据的方法名,value 是对应的重写方法
    4. const instrumentations = shallow
    5. ? isReadonly
    6. ? shallowReadonlyInstrumentations
    7. : shallowInstrumentations
    8. : isReadonly
    9. ? readonlyInstrumentations
    10. : mutableInstrumentations
    11. // 收集类数据的代理 get handler,借助这个重写收集类数据的方法
    12. return (
    13. target: CollectionTypes,
    14. key: string | symbol,
    15. receiver: CollectionTypes
    16. ) => {
    17. if (key === ReactiveFlags.IS_REACTIVE) {
    18. return !isReadonly
    19. } else if (key === ReactiveFlags.IS_READONLY) {
    20. return isReadonly
    21. } else if (key === ReactiveFlags.RAW) {
    22. return target
    23. }
    24. // 如果 key 是需要重写的方法名时,返回 instrumentations[key](返回重写的方法)
    25. return Reflect.get(
    26. hasOwn(instrumentations, key) && key in target
    27. ? instrumentations
    28. : target,
    29. key,
    30. receiver
    31. )
    32. }
    33. }

    当第一个参数为 true 时,instrumentations 等于 shallowReadonlyInstrumentations 或者 readonlyInstrumentations,以 readonlyInstrumentations 为例。

    1. const readonlyInstrumentations: Record<string, Function> = {
    2. get(this: MapTypes, key: unknown) {
    3. return get(this, key, true)
    4. },
    5. get size() {
    6. return size(this as unknown as IterableCollections, true)
    7. },
    8. has(this: MapTypes, key: unknown) {
    9. return has.call(this, key, true)
    10. },
    11. add: createReadonlyMethod(TriggerOpTypes.ADD),
    12. set: createReadonlyMethod(TriggerOpTypes.SET),
    13. delete: createReadonlyMethod(TriggerOpTypes.DELETE),
    14. clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
    15. forEach: createForEach(true, false)
    16. }
    17. function createReadonlyMethod(type: TriggerOpTypes): Function {
    18. return function (this: CollectionTypes, ...args: unknown[]) {
    19. if (__DEV__) {
    20. const key = args[0] ? `on key "${args[0]}" ` : ``
    21. console.warn(
    22. `${capitalize(type)} operation ${key}failed: target is readonly.`,
    23. toRaw(this)
    24. )
    25. }
    26. return type === TriggerOpTypes.DELETE ? false : this
    27. }
    28. }

    可以发现,当调用集合类只读数据上面能够变更数据的方法时,重写的方法中并不会进行真正的变更操作,只会打印出只读的警告。

    1-5,watch

    watch 的源码解析点击这里

    1-6,watchEffect

    watchEffect 的底层实现和 watch 是一样的,都是通过 doWatch 函数实现功能,这里就不过多赘述了,这里说说 flush: 'pre' | 'post' | 'sync' 是如何实现功能的。

    当 flush 的值是 sync 时,回调函数会在响应式数据发生变化时同步执行

    当 flush 的值是 pre 或者 post 时,回调函数会利用事件循环异步的执行,pre 和 post 的不同点是 pre 的回调函数会在组件渲染前被触发执行,而 post 类型的回调函数会在组件渲染完成后触发执行;

    相关源码如下所示:

    1. function doWatch(
    2. source: WatchSource | WatchSource[] | WatchEffect | object,
    3. cb: WatchCallback | null,
    4. { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
    5. ): WatchStopHandle {
    6. let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
    7. const job: SchedulerJob = () => {
    8. ......
    9. }
    10. let scheduler: EffectScheduler
    11. if (flush === 'sync') {
    12. scheduler = job as any // the scheduler function gets called directly
    13. } else if (flush === 'post') {
    14. scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
    15. } else {
    16. // default: 'pre'
    17. scheduler = () => queuePreFlushCb(job)
    18. }
    19. const effect = new ReactiveEffect(getter, scheduler)
    20. ......
    21. }

    flush == 'sync'

    当 flush 的值是 sync 时,scheduler 直接指向 job 函数,然后将 scheduler 函数作为第二个参数实例化 ReactiveEffect,当相关的响应式数据发生变更时,会直接执行 job 函数,所以,当 flush 的值是 sync 时,会同步的执行回调函数。

    flush == 'pre',flush == 'post'

    当 flush 的值是 pre 或者 post 时,回调函数的执行时异步的,并且和组件渲染的时机有关,当值是 pre 时,回调函数会在组件渲染前触发执行,当值是 post 时,回调函数会在组件渲染后触发执行。

    内部的实现原理借助了 5 个用于存放回调函数的数组,分别是:

    1. const queue: SchedulerJob[] = []
    2. const pendingPreFlushCbs: SchedulerJob[] = []
    3. let activePreFlushCbs: SchedulerJob[] | null = null
    4. const pendingPostFlushCbs: SchedulerJob[] = []
    5. let activePostFlushCbs: SchedulerJob[] | null = null

    queue 数组的作用是:存放用于重新渲染组件的回调函数。

    pendingPreFlushCbs 和 activePreFlushCbs 数组的作用是:存放需要组件重新渲染前执行的回调函数。

    pendingPostFlushCbs 和 activePostFlushCbs 数组的作用是:存放需要组件重新渲染后执行的回调函数。

    这些数组中存放的回调函数都是异步执行的,接下来看,执行的代码。

    1. function queueFlush() {
    2. if (!isFlushing && !isFlushPending) {
    3. isFlushPending = true
    4. currentFlushPromise = resolvedPromise.then(flushJobs)
    5. }
    6. }

    利用 Promise 将 flushJobs 函数放入微任务队列中。

    1. function flushJobs(seen?: CountMap) {
    2. // 执行 pre 回调函数
    3. flushPreFlushCbs(seen)
    4. try {
    5. // 执行 queue 中的回调函数,也就是能够重新渲染组件的回调函数
    6. for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
    7. const job = queue[flushIndex]
    8. if (job && job.active !== false) {
    9. callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
    10. }
    11. }
    12. } finally {
    13. flushIndex = 0
    14. queue.length = 0
    15. // 组件渲染完成后,执行 post 回调函数
    16. flushPostFlushCbs(seen)
    17. isFlushing = false
    18. currentFlushPromise = null
    19. // some postFlushCb queued jobs!
    20. // keep flushing until it drains.
    21. if (
    22. queue.length ||
    23. pendingPreFlushCbs.length ||
    24. pendingPostFlushCbs.length
    25. ) {
    26. flushJobs(seen)
    27. }
    28. }
    29. }

    在 flushJobs 函数中,写执行 pre 数组中的回调函数,然后执行 queue 数组中的回调函数,queue 中的函数执行完成后,再执行 post 数组中的回调函数,这样就保证了 pre 类型的回调函数在组件渲染前执行,post 类型的回调函数在组件渲染后执行。

    1-7,watchPostEffect、watchSyncEffect

    这两个 API 的实现原理就没有什么好说的了,看上面两小节的内容即可。

    2,响应式: 工具

    这一部分 API 的内部实现原理很简单,主要是利用标志位属性实现功能,所谓的标志位属性就是一个普通的属性,由 Vue3 对这个属性的意义进行定义,属性的值是 true 或者 false,例如当一个对象的 __v_isRef 属性为 true 时,则表明这个对象是一个 Ref 值。

    2-1,isRef

    isRef 接口的官方文档点击这里,该接口的源码如下所示:

    1. class RefImpl {
    2. public readonly __v_isRef = true
    3. ......
    4. }
    1. export function isRef(r: any): r is Ref {
    2. return !!(r && r.__v_isRef === true)
    3. }

    实现原理很简单,当 r.__v_isRef 属性的值为 true 时,则表明 r 是一个 Ref 值。

    2-2,unref

    unref 接口的官方文档点击这里

    当参数是一个 ref 值时,读取并返回 ref.value,否则直接返回参数,源码如下所示:

    1. export function unref(ref: T | Ref): T {
    2. return isRef(ref) ? (ref.value as any) : ref
    3. }

    2-3,toRef

    toRef 接口的官方文档点击这里

    这个接口的本质是利用访问器属性对源属性做一层代理,实现很简单,源码如下所示:

    1. export function toRefextends object, K extends keyof T>(
    2. object: T,
    3. key: K,
    4. defaultValue?: T[K]
    5. ): ToRef {
    6. const val = object[key]
    7. return isRef(val)
    8. ? val
    9. : (new ObjectRefImpl(object, key, defaultValue) as any)
    10. }

    首先通过 object[key] 获取指定的属性值,然后判断 val 是不是一个 Ref,如果是的话,直接返回 val,如果不是的话,再通过 ObjectRefImpl 类做一层代理。

    1. class ObjectRefImplextends object, K extends keyof T> {
    2. public readonly __v_isRef = true
    3. constructor(
    4. private readonly _object: T,
    5. private readonly _key: K,
    6. private readonly _defaultValue?: T[K]
    7. ) {}
    8. get value() {
    9. const val = this._object[this._key]
    10. return val === undefined ? (this._defaultValue as T[K]) : val
    11. }
    12. set value(newVal) {
    13. this._object[this._key] = newVal
    14. }
    15. }

    ObjectRefImpl 类中实现功能的重点在 value 访问器属性上,首先看 get value(){},通过 this._object[this._key] 获取目标属性的值,然后这个值是 undefined 的话,返回默认值,如果不是 undefined 的话,则返回 val。

    在 set value(){} 中,实现很简单, 直接将新值设置到 this._object[this._key] 上即可。

    2-4,toRefs

    toRefs 接口的官方文档点击这里

    toRefs 接口的实现更加简单,只需要遍历参数对象/数组,对每个值都调用 toRef 函数进行处理即可,源码如下所示:

    1. export function toRefsextends object>(object: T): ToRefs {
    2. if (__DEV__ && !isProxy(object)) {
    3. console.warn(`toRefs() expects a reactive object but received a plain one.`)
    4. }
    5. const ret: any = isArray(object) ? new Array(object.length) : {}
    6. for (const key in object) {
    7. ret[key] = toRef(object, key)
    8. }
    9. return ret
    10. }

    首先判断参数是不是代理对象,如果不是的话,则打印出警告,这里即使参数不是响应式对象,toRefs 也会进行处理,并没有直接 return。

    然后根据参数的数据类型,新建一个空数组或者空对象。

    接下来对参数的属性进行遍历,每个属性都调用 toRef 进行代理,并将代理后的数据设置到 ret 中,最后 return ret 即可。

    2-5,isProxy

    isProxy 接口的官方文档点击这里

    这个接口的实现原理是:根据标志位进行判断即可,源码如下所示:

    1. // 判断 value 是不是一个代理对象
    2. export function isProxy(value: unknown): boolean {
    3. // 使用的是 ||,所以下面的两个条件只要一个为 true,value 就会被判断为是一个代理对象。
    4. return isReactive(value) || isReadonly(value)
    5. }
    6. // 判断 value 是不是通过 reactive() 或者 shallowReactive() 创建出来的
    7. export function isReactive(value: unknown): boolean {
    8. // 我们可能将一个响应式对象作为 readonly 函数的参数创建出一个只读的代理对象
    9. // 像这种只读对象 isReactive 函数也认为它是通过 reactive 或者 shallowReactive 创建出来的
    10. // 判断 value 是不是只读的
    11. if (isReadonly(value)) {
    12. // 如果是的话,将 value[ReactiveFlags.RAW] 作为 isReactive 函数的参数递归进行判断
    13. return isReactive((value as Target)[ReactiveFlags.RAW])
    14. }
    15. // 判断 value 中的 [ReactiveFlags.IS_REACTIVE] 属性标志位是否为 true 即可。
    16. return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
    17. }
    18. // 判断 value 是不是只读的
    19. // 判断 value[ReactiveFlags.IS_READONLY] 属性标志位是否为 true 即可。
    20. export function isReadonly(value: unknown): boolean {
    21. return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
    22. }

    源码解读看注释即可。

    2-6,isReactive

    1. // 判断 value 是不是通过 reactive() 或者 shallowReactive() 创建出来的
    2. export function isReactive(value: unknown): boolean {
    3. // 我们可能将一个响应式对象作为 readonly 函数的参数创建出一个只读的代理对象
    4. // 像这种只读对象 isReactive 函数也认为它是通过 reactive 或者 shallowReactive 创建出来的
    5. // 判断 value 是不是只读的
    6. if (isReadonly(value)) {
    7. // 如果是的话,将 value[ReactiveFlags.RAW] 作为 isReactive 函数的参数递归进行判断
    8. return isReactive((value as Target)[ReactiveFlags.RAW])
    9. }
    10. // 判断 value 中的 [ReactiveFlags.IS_REACTIVE] 属性标志位是否为 true 即可。
    11. return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
    12. }

    2-7,isReadonly

    1. // 判断 value 是不是只读的
    2. // 判断 value[ReactiveFlags.IS_READONLY] 属性标志位是否为 true 即可。
    3. export function isReadonly(value: unknown): boolean {
    4. return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
    5. }

    3,响应式: 进阶

    3-1,shallowRef

    shallowRef 接口的官方文档点击这里

    建议先看下我的上一篇博客

    源码如下所示:

    1. export function shallowRef(value?: unknown) {
    2. // 这里需要关注的点是,createRef 函数的第二个参数为 true,这表明创建的是一个浅的 Ref
    3. return createRef(value, true)
    4. }
    1. function createRef(rawValue: unknown, shallow: boolean) {
    2. if (isRef(rawValue)) {
    3. return rawValue
    4. }
    5. return new RefImpl(rawValue, shallow)
    6. }
    1. class RefImpl {
    2. private _value: T
    3. private _rawValue: T
    4. public dep?: Dep = undefined
    5. public readonly __v_isRef = true
    6. constructor(value: T, public readonly __v_isShallow: boolean) {
    7. this._rawValue = __v_isShallow ? value : toRaw(value)
    8. this._value = __v_isShallow ? value : toReactive(value)
    9. }
    10. get value() {
    11. trackRefValue(this)
    12. return this._value
    13. }
    14. set value(newVal) {
    15. newVal = this.__v_isShallow ? newVal : toRaw(newVal)
    16. if (hasChanged(newVal, this._rawValue)) {
    17. this._rawValue = newVal
    18. this._value = this.__v_isShallow ? newVal : toReactive(newVal)
    19. triggerRefValue(this, newVal)
    20. }
    21. }
    22. }

    实现功能的重点在 RefImpl 类中,在构造函数中,如果 __v_isShallow 属性为 true 的话,则不需要将 value 进行响应式的处理,这保证了对数据深层次的读写不会进行依赖收集以及依赖更新。

    3-2,triggerRef

    接口的官方文档点击这里

    该接口的作用是:手动触发一个浅层 Ref 依赖的重新执行。

    源码如下所示:

    1. export function triggerRef(ref: Ref) {
    2. triggerRefValue(ref, __DEV__ ? ref.value : void 0)
    3. }
    1. export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
    2. ref = toRaw(ref)
    3. if (ref.dep) {
    4. triggerEffects(ref.dep)
    5. }
    6. }
    1. export function triggerEffects(
    2. dep: Dep | ReactiveEffect[],
    3. debuggerEventExtraInfo?: DebuggerEventExtraInfo
    4. ) {
    5. // spread into array for stabilization
    6. const effects = isArray(dep) ? dep : [...dep]
    7. for (const effect of effects) {
    8. if (effect.computed) {
    9. triggerEffect(effect, debuggerEventExtraInfo)
    10. }
    11. }
    12. for (const effect of effects) {
    13. if (!effect.computed) {
    14. triggerEffect(effect, debuggerEventExtraInfo)
    15. }
    16. }
    17. }
    18. function triggerEffect(
    19. effect: ReactiveEffect,
    20. debuggerEventExtraInfo?: DebuggerEventExtraInfo
    21. ) {
    22. if (effect !== activeEffect || effect.allowRecurse) {
    23. if (__DEV__ && effect.onTrigger) {
    24. effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
    25. }
    26. if (effect.scheduler) {
    27. effect.scheduler()
    28. } else {
    29. effect.run()
    30. }
    31. }
    32. }

    实现原理很简单,首先获取 Ref 值的 dep 属性,这个 dep 属性保存着使用了当前 ref 值的依赖(ReactiveEffect 实例),然后遍历 dep,触发 dep 重新执行即可。

    3-3,customRef

    customRef 的官方文档点击这里

    customRef 接口的作用是让用户拥有能够重写 ref value 访问器属性的能力。

    源码如下所示:

    1. export function customRef(factory: CustomRefFactory): Ref {
    2. return new CustomRefImpl(factory) as any
    3. }
    1. class CustomRefImpl {
    2. public dep?: Dep = undefined
    3. private readonly _get: ReturnType<CustomRefFactory>['get']
    4. private readonly _set: ReturnType<CustomRefFactory>['set']
    5. public readonly __v_isRef = true
    6. constructor(factory: CustomRefFactory) {
    7. const { get, set } = factory(
    8. () => trackRefValue(this),
    9. () => triggerRefValue(this)
    10. )
    11. this._get = get
    12. this._set = set
    13. }
    14. get value() {
    15. return this._get()
    16. }
    17. set value(newVal) {
    18. this._set(newVal)
    19. }
    20. }
    21. export function trackRefValue(ref: RefBase<any>) {
    22. if (shouldTrack && activeEffect) {
    23. ref = toRaw(ref)
    24. trackEffects(ref.dep || (ref.dep = createDep()))
    25. }
    26. }
    27. export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
    28. ref = toRaw(ref)
    29. if (ref.dep) {
    30. triggerEffects(ref.dep)
    31. }
    32. }

    首先看最下面的两个工具函数,这两个函数的作用是对指定的 Ref 值进行依赖追踪和触发依赖,参数是一个 Ref 对象。

    然后看 CustomRefImpl 的构造函数,我们需要执行 factory 函数,执行的时候,将能够进行依赖收集和触发依赖的两个函数作为参数传递进去,函数的返回值是一个对象,对象中有 get 和 set 两个函数,这两个函数就是用户重写的 value 计算属性函数,然后将 get 和 set 函数设置到对象实例的 _get 和 _set 属性上即可。

    最后,在 ref 的 value 计算属性内部分别执行 this._get() 和 this._set() 即可实现功能。

    3-4,shallowReactive

    接口的官方文档点击这里

    shallowReactive 不会对对象进行深层次的响应式转化,实现源码如下所示:

    1. export function shallowReactiveextends object>(
    2. target: T
    3. ): ShallowReactive {
    4. return createReactiveObject(
    5. target,
    6. false,
    7. shallowReactiveHandlers,
    8. shallowCollectionHandlers,
    9. shallowReactiveMap
    10. )
    11. }

    实现的关键在 shallowReactiveHandlers 和 shallowCollectionHandlers 中,这两个是 Proxy 的handlers。

    首先看 shallowReactiveHandlers。

    1. export const shallowReactiveHandlers = /*#__PURE__*/ extend(
    2. {},
    3. mutableHandlers,
    4. {
    5. get: shallowGet,
    6. set: shallowSet
    7. }
    8. )
    1. const shallowGet = /*#__PURE__*/ createGetter(false, true)
    2. function createGetter(isReadonly = false, shallow = false) {
    3. return function get(target: Target, key: string | symbol, receiver: object) {
    4. ......
    5. const res = Reflect.get(target, key, receiver)
    6. if (shallow) {
    7. return res
    8. }
    9. if (isObject(res)) {
    10. return isReadonly ? readonly(res) : reactive(res)
    11. }
    12. return res
    13. }
    14. }

    在 createGetter 函数中,如果 shallow 属性为 true 的话,则直接返回 res,如果 shallow 属性为 false 并且 res 是一个对象的话,会执行 reactive 函数进行深层次响应式的处理。

    接下来看 shallowCollectionHandlers。

    1. export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
    2. get: /*#__PURE__*/ createInstrumentationGetter(false, true)
    3. }
    1. // 创建收集类数据的 Proxy get handler
    2. function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
    3. // instrumentations 是一个对象,这个对象的 key 是收集类数据的方法名,value 是对应的重写方法
    4. const instrumentations = shallow
    5. ? isReadonly
    6. ? shallowReadonlyInstrumentations
    7. : shallowInstrumentations
    8. : isReadonly
    9. ? readonlyInstrumentations
    10. : mutableInstrumentations
    11. // 收集类数据的代理 get handler,借助这个重写收集类数据的方法
    12. return (
    13. target: CollectionTypes,
    14. key: string | symbol,
    15. receiver: CollectionTypes
    16. ) => {
    17. if (key === ReactiveFlags.IS_REACTIVE) {
    18. return !isReadonly
    19. } else if (key === ReactiveFlags.IS_READONLY) {
    20. return isReadonly
    21. } else if (key === ReactiveFlags.RAW) {
    22. return target
    23. }
    24. // 如果 key 是需要重写的方法名时,返回 instrumentations[key](返回重写的方法)
    25. return Reflect.get(
    26. hasOwn(instrumentations, key) && key in target
    27. ? instrumentations
    28. : target,
    29. key,
    30. receiver
    31. )
    32. }
    33. }

    这里,以 shallowInstrumentations 为例。

    1. const shallowInstrumentations: Record<string, Function> = {
    2. get(this: MapTypes, key: unknown) {
    3. return get(this, key, false, true)
    4. },
    5. get size() {
    6. return size(this as unknown as IterableCollections)
    7. },
    8. has,
    9. add,
    10. set,
    11. delete: deleteEntry,
    12. clear,
    13. forEach: createForEach(false, true)
    14. }
    1. function get(
    2. target: MapTypes,
    3. key: unknown,
    4. isReadonly = false,
    5. isShallow = false
    6. ) {
    7. // #1772: readonly(reactive(Map)) should return readonly + reactive version
    8. // of the value
    9. target = (target as any)[ReactiveFlags.RAW]
    10. const rawTarget = toRaw(target)
    11. const rawKey = toRaw(key)
    12. if (!isReadonly) {
    13. if (key !== rawKey) {
    14. track(rawTarget, TrackOpTypes.GET, key)
    15. }
    16. track(rawTarget, TrackOpTypes.GET, rawKey)
    17. }
    18. const { has } = getProto(rawTarget)
    19. const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    20. if (has.call(rawTarget, key)) {
    21. return wrap(target.get(key))
    22. } else if (has.call(rawTarget, rawKey)) {
    23. return wrap(target.get(rawKey))
    24. } else if (target !== rawTarget) {
    25. // #3602 readonly(reactive(Map))
    26. // ensure that the nested reactive `Map` can do tracking for itself
    27. target.get(key)
    28. }
    29. }
    30. const toShallow = extends unknown>(value: T): T => value
    31. export const toReactive = extends unknown>(value: T): T =>
    32. isObject(value) ? reactive(value) : value
    33. export const toReadonly = extends unknown>(value: T): T =>
    34. isObject(value) ? readonly(value as Record<any, any>) : value

    这里面主要看 const wrap 以下的代码,target.get(key) 是从收集类数据中获取到的数据,如果 isShallow 为 true 的话,wrap 函数的值指向 toShallow,我们可以发现 toShallow 函数并没有对返回值进行任何的处理,只是将参数直接返回出去。而当 isShallow 为 false 的话,wrap 的值指向 toReactive 或者 toReadonly,这两个函数会对返回值进行响应式或者只读的处理。

    结合上面两点,使用 shallowReactive 函数时,内部的数据不会进行响应式的处理,所以说 shallowReactive 是浅层的。

    3-5,shallowReadonly

    实现原理和 shallowReactive 一样,这里就不过多赘述了。

    3-6,toRaw

    官方文档点击这里

    当我们使用 reactive()、readonly()、shallowReactive()、shallowReadonly() 创建代理对象时,Vue 内部会将原始数据保存到代理对象的 __v_raw 属性上,所以 toRaw 的实现原理很简单,只要获取并返回代理对象的 __v_raw 属性即可,源码如下所示:

    1. export function toRaw(observed: T): T {
    2. const raw = observed && (observed as Target)[ReactiveFlags.RAW]
    3. return raw ? toRaw(raw) : observed
    4. }

    因为一个响应式的数据还可以被 readonly 处理,所以一个响应式数据的原始数据有可能是深层次的,所以在 toRaw 函数中,进行递归的调用进行处理这种情况。

    3-7,markRaw

    官方文档点击这里

    markRaw 的实现原理很简单,只是做一个标记而已,源码如下所示:

    1. export function markRawextends object>(
    2. value: T
    3. ): T & { [RawSymbol]?: true } {
    4. def(value, ReactiveFlags.SKIP, true)
    5. return value
    6. }
    7. export const def = (obj: object, key: string | symbol, value: any) => {
    8. Object.defineProperty(obj, key, {
    9. configurable: true,
    10. enumerable: false,
    11. value
    12. })
    13. }
    14. export const enum ReactiveFlags {
    15. SKIP = '__v_skip',
    16. IS_REACTIVE = '__v_isReactive',
    17. IS_READONLY = '__v_isReadonly',
    18. IS_SHALLOW = '__v_isShallow',
    19. RAW = '__v_raw'
    20. }

    在其他的接口进行响应式处理时,如果参数上的 __v_skip 属性的值为 true,则不会进行响应式的处理。

    1. function createReactiveObject(
    2. target: Target,
    3. isReadonly: boolean,
    4. baseHandlers: ProxyHandler<any>,
    5. collectionHandlers: ProxyHandler<any>,
    6. proxyMap: WeakMapany>
    7. ) {
    8. ......
    9. const targetType = getTargetType(target)
    10. // 如果当前的数据类型是 INVALID 的话,直接返回 target
    11. if (targetType === TargetType.INVALID) {
    12. return target
    13. }
    14. ......
    15. }
    16. function getTargetType(value: Target) {
    17. return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    18. ? TargetType.INVALID
    19. : targetTypeMap(toRawType(value))
    20. }

    3-8,effectScope

    官方文档点击这里

    effectScope 的核心思想和依赖收集是一样的,在依赖收集中,ReactiveEffect 是将自身实例挂在到全局作用域中,这样其他地方的代码都能够获取到当前激活的 ReactiveEffect 实例,然后进行依赖收集。

    effectScope 的核心思想是当执行 EffectScope 实例的 run 函数时,会将当前 EffectScope 实例挂载到全局上,然后当 new ReactiveEffect 的时候,将 ReactiveEffect 实例收集存储到 EffectScope 实例中。当执行 EffectScope 实例的 stop 方法时,EffectScope 实例会遍历执行收集的 ReactiveEffect 的 stop 方法,ReactiveEffect 的 stop 方法会进行依赖的重置操作,这样就实现了 effectScope API 的功能了。

    源码如下所示:

    1. let activeEffectScope: EffectScope | undefined
    2. export function effectScope(detached?: boolean) {
    3. return new EffectScope(detached)
    4. }
    5. export class EffectScope {
    6. active = true
    7. effects: ReactiveEffect[] = []
    8. constructor(detached = false) {
    9. }
    10. run(fn: () => T): T | undefined {
    11. if (this.active) {
    12. const currentEffectScope = activeEffectScope
    13. try {
    14. activeEffectScope = this
    15. return fn()
    16. } finally {
    17. activeEffectScope = currentEffectScope
    18. }
    19. } else if (__DEV__) {
    20. warn(`cannot run an inactive effect scope.`)
    21. }
    22. }
    23. stop(fromParent?: boolean) {
    24. if (this.active) {
    25. let i, l
    26. for (i = 0, l = this.effects.length; i < l; i++) {
    27. this.effects[i].stop()
    28. }
    29. this.active = false
    30. }
    31. }
    32. }
    33. export function recordEffectScope(
    34. effect: ReactiveEffect,
    35. scope: EffectScope | undefined = activeEffectScope
    36. ) {
    37. if (scope && scope.active) {
    38. scope.effects.push(effect)
    39. }
    40. }
    1. export class ReactiveEffectany> {
    2. constructor(
    3. public fn: () => T,
    4. public scheduler: EffectScheduler | null = null,
    5. scope?: EffectScope
    6. ) {
    7. recordEffectScope(this, scope)
    8. }
    9. stop() {
    10. if (this.active) {
    11. cleanupEffect(this)
    12. this.active = false
    13. }
    14. }
    15. }

    当执行 scope.run 函数时,其先将自身设置到全局变量 activeEffectScope 上,然后执行 fn 函数,fn 函数的执行会引起 ReactiveEffect 类的实例化,在 ReactiveEffect 类的构造函数中,执行了 recordEffectScope 工具函数,这个函数的作用是将 ReactiveEffect 实例收集到 EffectScope 实例的 effects 属性中。

    当执行 scope.stop 方法时,内部会遍历执行 this.effects 中 ReactiveEffect 实例的 stop 方法,这个方法会重置响应式数据和 ReactiveEffect 实例的依赖收集关系。

    3-9,getCurrentScope

    官方文档点击这里

    1. let activeEffectScope: EffectScope | undefined
    2. export function getCurrentScope() {
    3. return activeEffectScope
    4. }

    3-10,onScopeDispose

    官方文档点击这里

    1. let activeEffectScope: EffectScope | undefined
    2. export function onScopeDispose(fn: () => void) {
    3. if (activeEffectScope) {
    4. activeEffectScope.cleanups.push(fn)
    5. }
    6. }
    1. export class EffectScope {
    2. active = true
    3. effects: ReactiveEffect[] = []
    4. cleanups: (() => void)[] = []
    5. constructor(detached = false) {
    6. }
    7. run(fn: () => T): T | undefined {
    8. if (this.active) {
    9. const currentEffectScope = activeEffectScope
    10. try {
    11. activeEffectScope = this
    12. return fn()
    13. } finally {
    14. activeEffectScope = currentEffectScope
    15. }
    16. } else if (__DEV__) {
    17. warn(`cannot run an inactive effect scope.`)
    18. }
    19. }
    20. stop(fromParent?: boolean) {
    21. if (this.active) {
    22. let i, l
    23. for (i = 0, l = this.effects.length; i < l; i++) {
    24. this.effects[i].stop()
    25. }
    26. for (i = 0, l = this.cleanups.length; i < l; i++) {
    27. this.cleanups[i]()
    28. }
    29. this.active = false
    30. }
    31. }
    32. }

    这个 API 很简单,只需要将函数参数 push 到 activeEffectScope.cleanups 数组中即可,后续执行 scope.stop 方法时,遍历执行 cleanups 数组中的函数即可。

  • 相关阅读:
    Linux 命令行的世界 :4.操作文件和目录
    Docker 容器 jvm 内存参数调整优化
    14、Java——迷你图书管理器(对象+数组)
    408操作系统笔记
    Scala入门到精通(尚硅谷学习笔记)章节一——scala入门
    CE修改植物大战僵尸-阳光基址(小宇特详解)
    Go开源世界主流成熟ORM框架gorm实践分享
    安全狗“指南” | XDM,收好这份攻防演练“摸鱼指南”
    图片隐写之LSB(Least Significant Bit)原理及其代码实现
    JS之同步异步promise、async、await
  • 原文地址:https://blog.csdn.net/f18855666661/article/details/126346843