这篇文章详细讲讲 Vue3 中的响应式 API,如下图所示:
ref 的源码解析点击这里。
computed 的源码解析点击这里。
源码如下所示,代码解释在注释中。
export function reactive(target: object) {
// 如果当前的 target 参数是只读数据的话,直接 return target。
// 只读数据无需转换成响应式的,因为只读数据不会变更,进而也就不会触发依赖执行,响应式的特征没有意义
if (isReadonly(target)) {
return target
}
// 调用 createReactiveObject 函数完成响应式数据的转换
return createReactiveObject(
// 转换的目标对象
target,
// 是否是只读的
false,
// 用于处理 Object 和 Array 数据类型的 Proxy handler
mutableHandlers,
// 用于处理 Map、Set、WeakMap、WeakSet 数据类型的 Proxy handler
mutableCollectionHandlers,
// 一个 Map 数据,键是转换成响应式的原生对象,值是其对应的响应式对象。
// 这个数据起到一个响应式对象缓存的作用
reactiveMap
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler,
collectionHandlers: ProxyHandler,
proxyMap: WeakMap
) {
// 判断 target 是不是对象类型,如果不是的话,直接 return target
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// 如果 target 已经是一个代理数据的话,直接返回它。
// 例外情况:readonly 类型的数据也会被当做响应式对象,直接返回。
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 查看当前的 target 是不是已经被转化成响应式数据了,如果已经被转化成响应式数据的话,
// 则直接返回缓存中的对应代理对象。
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 获取当前 target 的数据类型,数据类型有三大类,第一类是常规代理数据,如:Object、Array
// 第二大类是收集类,如:Map、Set、WeakMap、WeakSet
// 第三大类是非法类,也就是说除了上面 6 种数据类型,其他数据都无法转换成响应式的。
const targetType = getTargetType(target)
// 如果当前的数据类型是 INVALID 的话,直接返回 target
if (targetType === TargetType.INVALID) {
return target
}
// 创建 Proxy 对象,并且根据 target 数据类型使用不同的 handler
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 将当前转换的响应式数据缓存到 proxyMap 中
proxyMap.set(target, proxy)
// 返回代理响应式数据
return proxy
}
const enum TargetType {
INVALID = 0,
COMMON = 1,
COLLECTION = 2
}
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
baseHandlers 和 collectionHandlers handler 如下所示:
// baseHandlers
export const mutableHandlers: ProxyHandler
createInstrumentationGetter 函数的内容如下所示:
const [
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
shallowReadonlyInstrumentations
] = /* #__PURE__*/ createInstrumentations()
// 创建收集类数据的 Proxy get handler
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
// instrumentations 是一个对象,这个对象的 key 是收集类数据的方法名,value 是对应的重写方法
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
// 收集类数据的代理 get handler,借助这个重写收集类数据的方法
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
) => {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}
// 如果 key 是需要重写的方法名时,返回 instrumentations[key](返回重写的方法)
return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
readonly 的源码如下所示:
export function readonly(
target: T
): DeepReadonly> {
return createReactiveObject(
target,
true,
readonlyHandlers,
readonlyCollectionHandlers,
readonlyMap
)
}
实现功能的重点在 readonlyHandlers 和 readonlyCollectionHandlers 这两个只读专属的 Proxy handlers。
readonlyHandlers 的内容如下所示:
export const readonlyHandlers: ProxyHandler
可以发现,实现很简单,当我们想变更数据的时候,打印出只读的警告,并且不做任何变更操作,直接 return true。
readonlyCollectionHandlers 的内容如下所示:
export const readonlyCollectionHandlers: ProxyHandler = {
get: /*#__PURE__*/ createInstrumentationGetter(true, false)
}
可以发现,还是调用 createInstrumentationGetter 函数,只不过第一个参数 isReadonly 为 true。
// 创建收集类数据的 Proxy get handler
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
// instrumentations 是一个对象,这个对象的 key 是收集类数据的方法名,value 是对应的重写方法
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
// 收集类数据的代理 get handler,借助这个重写收集类数据的方法
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
) => {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}
// 如果 key 是需要重写的方法名时,返回 instrumentations[key](返回重写的方法)
return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
当第一个参数为 true 时,instrumentations 等于 shallowReadonlyInstrumentations 或者 readonlyInstrumentations,以 readonlyInstrumentations 为例。
const readonlyInstrumentations: Record = {
get(this: MapTypes, key: unknown) {
return get(this, key, true)
},
get size() {
return size(this as unknown as IterableCollections, true)
},
has(this: MapTypes, key: unknown) {
return has.call(this, key, true)
},
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true, false)
}
function createReadonlyMethod(type: TriggerOpTypes): Function {
return function (this: CollectionTypes, ...args: unknown[]) {
if (__DEV__) {
const key = args[0] ? `on key "${args[0]}" ` : ``
console.warn(
`${capitalize(type)} operation ${key}failed: target is readonly.`,
toRaw(this)
)
}
return type === TriggerOpTypes.DELETE ? false : this
}
}
可以发现,当调用集合类只读数据上面能够变更数据的方法时,重写的方法中并不会进行真正的变更操作,只会打印出只读的警告。
watch 的源码解析点击这里。
watchEffect 的底层实现和 watch 是一样的,都是通过 doWatch 函数实现功能,这里就不过多赘述了,这里说说 flush: 'pre' | 'post' | 'sync' 是如何实现功能的。
当 flush 的值是 sync 时,回调函数会在响应式数据发生变化时同步执行;
当 flush 的值是 pre 或者 post 时,回调函数会利用事件循环异步的执行,pre 和 post 的不同点是 pre 的回调函数会在组件渲染前被触发执行,而 post 类型的回调函数会在组件渲染完成后触发执行;
相关源码如下所示:
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
const job: SchedulerJob = () => {
......
}
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// default: 'pre'
scheduler = () => queuePreFlushCb(job)
}
const effect = new ReactiveEffect(getter, scheduler)
......
}
flush == 'sync'
当 flush 的值是 sync 时,scheduler 直接指向 job 函数,然后将 scheduler 函数作为第二个参数实例化 ReactiveEffect,当相关的响应式数据发生变更时,会直接执行 job 函数,所以,当 flush 的值是 sync 时,会同步的执行回调函数。
flush == 'pre',flush == 'post'
当 flush 的值是 pre 或者 post 时,回调函数的执行时异步的,并且和组件渲染的时机有关,当值是 pre 时,回调函数会在组件渲染前触发执行,当值是 post 时,回调函数会在组件渲染后触发执行。
内部的实现原理借助了 5 个用于存放回调函数的数组,分别是:
const queue: SchedulerJob[] = []
const pendingPreFlushCbs: SchedulerJob[] = []
let activePreFlushCbs: SchedulerJob[] | null = null
const pendingPostFlushCbs: SchedulerJob[] = []
let activePostFlushCbs: SchedulerJob[] | null = null
queue 数组的作用是:存放用于重新渲染组件的回调函数。
pendingPreFlushCbs 和 activePreFlushCbs 数组的作用是:存放需要组件重新渲染前执行的回调函数。
pendingPostFlushCbs 和 activePostFlushCbs 数组的作用是:存放需要组件重新渲染后执行的回调函数。
这些数组中存放的回调函数都是异步执行的,接下来看,执行的代码。
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true
currentFlushPromise = resolvedPromise.then(flushJobs)
}
}
利用 Promise 将 flushJobs 函数放入微任务队列中。
function flushJobs(seen?: CountMap) {
// 执行 pre 回调函数
flushPreFlushCbs(seen)
try {
// 执行 queue 中的回调函数,也就是能够重新渲染组件的回调函数
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex]
if (job && job.active !== false) {
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
}
}
} finally {
flushIndex = 0
queue.length = 0
// 组件渲染完成后,执行 post 回调函数
flushPostFlushCbs(seen)
isFlushing = false
currentFlushPromise = null
// some postFlushCb queued jobs!
// keep flushing until it drains.
if (
queue.length ||
pendingPreFlushCbs.length ||
pendingPostFlushCbs.length
) {
flushJobs(seen)
}
}
}
在 flushJobs 函数中,写执行 pre 数组中的回调函数,然后执行 queue 数组中的回调函数,queue 中的函数执行完成后,再执行 post 数组中的回调函数,这样就保证了 pre 类型的回调函数在组件渲染前执行,post 类型的回调函数在组件渲染后执行。
这两个 API 的实现原理就没有什么好说的了,看上面两小节的内容即可。
这一部分 API 的内部实现原理很简单,主要是利用标志位属性实现功能,所谓的标志位属性就是一个普通的属性,由 Vue3 对这个属性的意义进行定义,属性的值是 true 或者 false,例如当一个对象的 __v_isRef 属性为 true 时,则表明这个对象是一个 Ref 值。
isRef 接口的官方文档点击这里,该接口的源码如下所示:
class RefImpl {
public readonly __v_isRef = true
......
}
export function isRef(r: any): r is Ref {
return !!(r && r.__v_isRef === true)
}
实现原理很简单,当 r.__v_isRef 属性的值为 true 时,则表明 r 是一个 Ref 值。
unref 接口的官方文档点击这里。
当参数是一个 ref 值时,读取并返回 ref.value,否则直接返回参数,源码如下所示:
export function unref(ref: T | Ref): T {
return isRef(ref) ? (ref.value as any) : ref
}
toRef 接口的官方文档点击这里。
这个接口的本质是利用访问器属性对源属性做一层代理,实现很简单,源码如下所示:
export function toRef(
object: T,
key: K,
defaultValue?: T[K]
): ToRef {
const val = object[key]
return isRef(val)
? val
: (new ObjectRefImpl(object, key, defaultValue) as any)
}
首先通过 object[key] 获取指定的属性值,然后判断 val 是不是一个 Ref,如果是的话,直接返回 val,如果不是的话,再通过 ObjectRefImpl 类做一层代理。
class ObjectRefImpl {
public readonly __v_isRef = true
constructor(
private readonly _object: T,
private readonly _key: K,
private readonly _defaultValue?: T[K]
) {}
get value() {
const val = this._object[this._key]
return val === undefined ? (this._defaultValue as T[K]) : val
}
set value(newVal) {
this._object[this._key] = newVal
}
}
ObjectRefImpl 类中实现功能的重点在 value 访问器属性上,首先看 get value(){},通过 this._object[this._key] 获取目标属性的值,然后这个值是 undefined 的话,返回默认值,如果不是 undefined 的话,则返回 val。
在 set value(){} 中,实现很简单, 直接将新值设置到 this._object[this._key] 上即可。
toRefs 接口的官方文档点击这里。
toRefs 接口的实现更加简单,只需要遍历参数对象/数组,对每个值都调用 toRef 函数进行处理即可,源码如下所示:
export function toRefs(object: T): ToRefs {
if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`)
}
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}
首先判断参数是不是代理对象,如果不是的话,则打印出警告,这里即使参数不是响应式对象,toRefs 也会进行处理,并没有直接 return。
然后根据参数的数据类型,新建一个空数组或者空对象。
接下来对参数的属性进行遍历,每个属性都调用 toRef 进行代理,并将代理后的数据设置到 ret 中,最后 return ret 即可。
isProxy 接口的官方文档点击这里。
这个接口的实现原理是:根据标志位进行判断即可,源码如下所示:
// 判断 value 是不是一个代理对象
export function isProxy(value: unknown): boolean {
// 使用的是 ||,所以下面的两个条件只要一个为 true,value 就会被判断为是一个代理对象。
return isReactive(value) || isReadonly(value)
}
// 判断 value 是不是通过 reactive() 或者 shallowReactive() 创建出来的
export function isReactive(value: unknown): boolean {
// 我们可能将一个响应式对象作为 readonly 函数的参数创建出一个只读的代理对象
// 像这种只读对象 isReactive 函数也认为它是通过 reactive 或者 shallowReactive 创建出来的
// 判断 value 是不是只读的
if (isReadonly(value)) {
// 如果是的话,将 value[ReactiveFlags.RAW] 作为 isReactive 函数的参数递归进行判断
return isReactive((value as Target)[ReactiveFlags.RAW])
}
// 判断 value 中的 [ReactiveFlags.IS_REACTIVE] 属性标志位是否为 true 即可。
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
// 判断 value 是不是只读的
// 判断 value[ReactiveFlags.IS_READONLY] 属性标志位是否为 true 即可。
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
源码解读看注释即可。
// 判断 value 是不是通过 reactive() 或者 shallowReactive() 创建出来的
export function isReactive(value: unknown): boolean {
// 我们可能将一个响应式对象作为 readonly 函数的参数创建出一个只读的代理对象
// 像这种只读对象 isReactive 函数也认为它是通过 reactive 或者 shallowReactive 创建出来的
// 判断 value 是不是只读的
if (isReadonly(value)) {
// 如果是的话,将 value[ReactiveFlags.RAW] 作为 isReactive 函数的参数递归进行判断
return isReactive((value as Target)[ReactiveFlags.RAW])
}
// 判断 value 中的 [ReactiveFlags.IS_REACTIVE] 属性标志位是否为 true 即可。
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
// 判断 value 是不是只读的
// 判断 value[ReactiveFlags.IS_READONLY] 属性标志位是否为 true 即可。
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
shallowRef 接口的官方文档点击这里。
建议先看下我的上一篇博客。
源码如下所示:
export function shallowRef(value?: unknown) {
// 这里需要关注的点是,createRef 函数的第二个参数为 true,这表明创建的是一个浅的 Ref
return createRef(value, true)
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
newVal = this.__v_isShallow ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
实现功能的重点在 RefImpl 类中,在构造函数中,如果 __v_isShallow 属性为 true 的话,则不需要将 value 进行响应式的处理,这保证了对数据深层次的读写不会进行依赖收集以及依赖更新。
接口的官方文档点击这里。
该接口的作用是:手动触发一个浅层 Ref 依赖的重新执行。
源码如下所示:
export function triggerRef(ref: Ref) {
triggerRefValue(ref, __DEV__ ? ref.value : void 0)
}
export function triggerRefValue(ref: RefBase, newVal?: any) {
ref = toRaw(ref)
if (ref.dep) {
triggerEffects(ref.dep)
}
}
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
const effects = isArray(dep) ? dep : [...dep]
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
}
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
实现原理很简单,首先获取 Ref 值的 dep 属性,这个 dep 属性保存着使用了当前 ref 值的依赖(ReactiveEffect 实例),然后遍历 dep,触发 dep 重新执行即可。
customRef 的官方文档点击这里。
customRef 接口的作用是让用户拥有能够重写 ref value 访问器属性的能力。
源码如下所示:
export function customRef(factory: CustomRefFactory): Ref {
return new CustomRefImpl(factory) as any
}
class CustomRefImpl {
public dep?: Dep = undefined
private readonly _get: ReturnType>['get']
private readonly _set: ReturnType>['set']
public readonly __v_isRef = true
constructor(factory: CustomRefFactory) {
const { get, set } = factory(
() => trackRefValue(this),
() => triggerRefValue(this)
)
this._get = get
this._set = set
}
get value() {
return this._get()
}
set value(newVal) {
this._set(newVal)
}
}
export function trackRefValue(ref: RefBase) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
export function triggerRefValue(ref: RefBase, newVal?: any) {
ref = toRaw(ref)
if (ref.dep) {
triggerEffects(ref.dep)
}
}
首先看最下面的两个工具函数,这两个函数的作用是对指定的 Ref 值进行依赖追踪和触发依赖,参数是一个 Ref 对象。
然后看 CustomRefImpl 的构造函数,我们需要执行 factory 函数,执行的时候,将能够进行依赖收集和触发依赖的两个函数作为参数传递进去,函数的返回值是一个对象,对象中有 get 和 set 两个函数,这两个函数就是用户重写的 value 计算属性函数,然后将 get 和 set 函数设置到对象实例的 _get 和 _set 属性上即可。
最后,在 ref 的 value 计算属性内部分别执行 this._get() 和 this._set() 即可实现功能。
接口的官方文档点击这里。
shallowReactive 不会对对象进行深层次的响应式转化,实现源码如下所示:
export function shallowReactive(
target: T
): ShallowReactive {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers,
shallowReactiveMap
)
}
实现的关键在 shallowReactiveHandlers 和 shallowCollectionHandlers 中,这两个是 Proxy 的handlers。
首先看 shallowReactiveHandlers。
export const shallowReactiveHandlers = /*#__PURE__*/ extend(
{},
mutableHandlers,
{
get: shallowGet,
set: shallowSet
}
)
const shallowGet = /*#__PURE__*/ createGetter(false, true)
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
......
const res = Reflect.get(target, key, receiver)
if (shallow) {
return res
}
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
在 createGetter 函数中,如果 shallow 属性为 true 的话,则直接返回 res,如果 shallow 属性为 false 并且 res 是一个对象的话,会执行 reactive 函数进行深层次响应式的处理。
接下来看 shallowCollectionHandlers。
export const shallowCollectionHandlers: ProxyHandler = {
get: /*#__PURE__*/ createInstrumentationGetter(false, true)
}
// 创建收集类数据的 Proxy get handler
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
// instrumentations 是一个对象,这个对象的 key 是收集类数据的方法名,value 是对应的重写方法
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
// 收集类数据的代理 get handler,借助这个重写收集类数据的方法
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
) => {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}
// 如果 key 是需要重写的方法名时,返回 instrumentations[key](返回重写的方法)
return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
这里,以 shallowInstrumentations 为例。
const shallowInstrumentations: Record = {
get(this: MapTypes, key: unknown) {
return get(this, key, false, true)
},
get size() {
return size(this as unknown as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, true)
}
function get(
target: MapTypes,
key: unknown,
isReadonly = false,
isShallow = false
) {
// #1772: readonly(reactive(Map)) should return readonly + reactive version
// of the value
target = (target as any)[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
if (!isReadonly) {
if (key !== rawKey) {
track(rawTarget, TrackOpTypes.GET, key)
}
track(rawTarget, TrackOpTypes.GET, rawKey)
}
const { has } = getProto(rawTarget)
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
if (has.call(rawTarget, key)) {
return wrap(target.get(key))
} else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey))
} else if (target !== rawTarget) {
// #3602 readonly(reactive(Map))
// ensure that the nested reactive `Map` can do tracking for itself
target.get(key)
}
}
const toShallow = (value: T): T => value
export const toReactive = (value: T): T =>
isObject(value) ? reactive(value) : value
export const toReadonly = (value: T): T =>
isObject(value) ? readonly(value as Record) : value
这里面主要看 const wrap 以下的代码,target.get(key) 是从收集类数据中获取到的数据,如果 isShallow 为 true 的话,wrap 函数的值指向 toShallow,我们可以发现 toShallow 函数并没有对返回值进行任何的处理,只是将参数直接返回出去。而当 isShallow 为 false 的话,wrap 的值指向 toReactive 或者 toReadonly,这两个函数会对返回值进行响应式或者只读的处理。
结合上面两点,使用 shallowReactive 函数时,内部的数据不会进行响应式的处理,所以说 shallowReactive 是浅层的。
实现原理和 shallowReactive 一样,这里就不过多赘述了。
官方文档点击这里。
当我们使用 reactive()、readonly()、shallowReactive()、shallowReadonly() 创建代理对象时,Vue 内部会将原始数据保存到代理对象的 __v_raw 属性上,所以 toRaw 的实现原理很简单,只要获取并返回代理对象的 __v_raw 属性即可,源码如下所示:
export function toRaw(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
因为一个响应式的数据还可以被 readonly 处理,所以一个响应式数据的原始数据有可能是深层次的,所以在 toRaw 函数中,进行递归的调用进行处理这种情况。
官方文档点击这里。
markRaw 的实现原理很简单,只是做一个标记而已,源码如下所示:
export function markRaw(
value: T
): T & { [RawSymbol]?: true } {
def(value, ReactiveFlags.SKIP, true)
return value
}
export const def = (obj: object, key: string | symbol, value: any) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value
})
}
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw'
}
在其他的接口进行响应式处理时,如果参数上的 __v_skip 属性的值为 true,则不会进行响应式的处理。
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler,
collectionHandlers: ProxyHandler,
proxyMap: WeakMap
) {
......
const targetType = getTargetType(target)
// 如果当前的数据类型是 INVALID 的话,直接返回 target
if (targetType === TargetType.INVALID) {
return target
}
......
}
function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
官方文档点击这里。
effectScope 的核心思想和依赖收集是一样的,在依赖收集中,ReactiveEffect 是将自身实例挂在到全局作用域中,这样其他地方的代码都能够获取到当前激活的 ReactiveEffect 实例,然后进行依赖收集。
effectScope 的核心思想是当执行 EffectScope 实例的 run 函数时,会将当前 EffectScope 实例挂载到全局上,然后当 new ReactiveEffect 的时候,将 ReactiveEffect 实例收集存储到 EffectScope 实例中。当执行 EffectScope 实例的 stop 方法时,EffectScope 实例会遍历执行收集的 ReactiveEffect 的 stop 方法,ReactiveEffect 的 stop 方法会进行依赖的重置操作,这样就实现了 effectScope API 的功能了。
源码如下所示:
let activeEffectScope: EffectScope | undefined
export function effectScope(detached?: boolean) {
return new EffectScope(detached)
}
export class EffectScope {
active = true
effects: ReactiveEffect[] = []
constructor(detached = false) {
}
run(fn: () => T): T | undefined {
if (this.active) {
const currentEffectScope = activeEffectScope
try {
activeEffectScope = this
return fn()
} finally {
activeEffectScope = currentEffectScope
}
} else if (__DEV__) {
warn(`cannot run an inactive effect scope.`)
}
}
stop(fromParent?: boolean) {
if (this.active) {
let i, l
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].stop()
}
this.active = false
}
}
}
export function recordEffectScope(
effect: ReactiveEffect,
scope: EffectScope | undefined = activeEffectScope
) {
if (scope && scope.active) {
scope.effects.push(effect)
}
}
export class ReactiveEffect {
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
stop() {
if (this.active) {
cleanupEffect(this)
this.active = false
}
}
}
当执行 scope.run 函数时,其先将自身设置到全局变量 activeEffectScope 上,然后执行 fn 函数,fn 函数的执行会引起 ReactiveEffect 类的实例化,在 ReactiveEffect 类的构造函数中,执行了 recordEffectScope 工具函数,这个函数的作用是将 ReactiveEffect 实例收集到 EffectScope 实例的 effects 属性中。
当执行 scope.stop 方法时,内部会遍历执行 this.effects 中 ReactiveEffect 实例的 stop 方法,这个方法会重置响应式数据和 ReactiveEffect 实例的依赖收集关系。
官方文档点击这里。
let activeEffectScope: EffectScope | undefined
export function getCurrentScope() {
return activeEffectScope
}
官方文档点击这里。
let activeEffectScope: EffectScope | undefined
export function onScopeDispose(fn: () => void) {
if (activeEffectScope) {
activeEffectScope.cleanups.push(fn)
}
}
export class EffectScope {
active = true
effects: ReactiveEffect[] = []
cleanups: (() => void)[] = []
constructor(detached = false) {
}
run(fn: () => T): T | undefined {
if (this.active) {
const currentEffectScope = activeEffectScope
try {
activeEffectScope = this
return fn()
} finally {
activeEffectScope = currentEffectScope
}
} else if (__DEV__) {
warn(`cannot run an inactive effect scope.`)
}
}
stop(fromParent?: boolean) {
if (this.active) {
let i, l
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].stop()
}
for (i = 0, l = this.cleanups.length; i < l; i++) {
this.cleanups[i]()
}
this.active = false
}
}
}
这个 API 很简单,只需要将函数参数 push 到 activeEffectScope.cleanups 数组中即可,后续执行 scope.stop 方法时,遍历执行 cleanups 数组中的函数即可。