// 定义target属性
interface Target {
// 标记为原始数据,则不能进行监控,只能为原始数据
[ReactiveFlags.SKIP]?: boolean
// 是否是可读可写响应式
[ReactiveFlags.IS_REACTIVE]?: boolean
// 是否为只读响应式
[ReactiveFlags.IS_READONLY]?: boolean
// 指向原始数据
[ReactiveFlags.RAW]?: any
// 指向可读可写响应数据
[ReactiveFlags.REACTIVE]?: any
// 指向只读响应数据
[ReactiveFlags.READONLY]?: any
}
// 判断是否为可读可写响应式
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
// 如果是可读的的,以原始数据进行判断
return isReactive((value as Target)[ReactiveFlags.RAW])
}
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
// 判断是否为只读响应式
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
// 判断是否为代理数据
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
// 通过RAW属性进行获取原始数据,如果原始数据不存在直接返回监控数据
export function toRaw<T>(observed: T): T {
return (
(observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
)
}
主要定义了一些属性,用来判断target的状态,并且通过一些判断函数来看出属性的作用。
// 判断变量类型是否可以监控
const isObservableType = /*#__PURE__*/ makeMap(
'Object,Array,Map,Set,WeakMap,WeakSet'
)
// 主要作用是判断是否为可监控类型
export function makeMap(
str: string,
expectsLowerCase?: boolean
): (key: string) => boolean {
const map: Record<string, boolean> = Object.create(null)
const list: Array<string> = str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]
}
// 主要用于截取value的类型
export const toRawType = (value: unknown): string => {
return toTypeString(value).slice(8, -1)
}
export const objectToString = Object.prototype.toString
export const toTypeString = (value: unknown): string =>
objectToString.call(value)
// 判断是否可以监控
// 主要判断类型、是否冻结、是否标记为原始数据
const canObserve = (value: Target): boolean => {
return (
!value[ReactiveFlags.SKIP] &&
isObservableType(toRawType(value)) &&
!Object.isFrozen(value)
)
}
是否可以监控的判断比较简单,主要从是否标记为原始类型、是否是可监控类型,是否冻结的几个维度进行判断是否可以监控。
// only unwrap nested ref
// UnwrapNestedRefs主要作用就是对象内部含有Ref会进行解构
type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
// 如果对象为Readonly,则不能创建Reactive,直接返回
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
Reactive指可读可写的响应式数据结构,调用createReactiveObject传参的isReadonly为false.
有同学会问了,reactive与ref同样做响应式数据,有什么区别呢。
1.ref可以接收简单数据类型,但是reactive只能接收对象(主要ref内部做了一层value值,将简单数据类型变成了对象)
2.ref返回数据需要通过value属性进行访问,而reactive如果接收非ref数据,可以直接进行访问(Ref主要针对简单数据的监控,而Reactive针对复杂数据的监控)
如果向reactive内部进行传递ref类型变量,同样会进行监听,这样ref是拥有一个监听,reactive又有一个监听代理,在测试例子中,reactive与ref中的set都会触发对应依赖,虽然effect中会依赖进行去重,但是考虑这块应该可以实现优化,今天去github做一个提问,看下有没有人有类似问题。
// DeepReadonly只是递归获取数据类型
// 递归获取数据类型主要通过Infer(推断类型)进行递归
// infer如果不理解可以查看
type Primitive = string | number | boolean | bigint | symbol | undefined | null
type Builtin = Primitive | Function | Date | Error | RegExp
export type DeepReadonly = T extends Builtin
? T
: T extends Map<infer K, infer V>
? ReadonlyMap<DeepReadonly, DeepReadonly>
: T extends ReadonlyMap<infer K, infer V>
? ReadonlyMap<DeepReadonly, DeepReadonly>
: T extends WeakMap<infer K, infer V>
? WeakMap<DeepReadonly, DeepReadonly>
: T extends Set
? ReadonlySet<DeepReadonly>
: T extends ReadonlySet
? ReadonlySet<DeepReadonly>
: T extends WeakSet
? WeakSet<DeepReadonly>
: T extends Promise
? Promise<DeepReadonly>
: T extends {}
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: Readonly
export function readonly(target: T)
: DeepReadonly<UnwrapNestedRefs> {
return createReactiveObject(
target,
true,
readonlyHandlers,
readonlyCollectionHandlers
)
}
Readonly指只读的响应式数据结构,调用调用createReactiveObject传参的isReadonly为true
上述主要难点是infer类型,举个例子:
DeepReadonly<Map<object, string>> ===>ReadonlyMap<DeepReadonly, DeepReadonly > 等等,主要是能获取其中key,value重新递归获取类型。
注意一下:
Reactive指既可以读也可以写的响应式;
Readonly指只能读的响应式;
以下将直接写Reactive与Readonly
// Target指需要转换为响应对象的数据
// isReadonly指是否想要转化为可读响应式
// baseHnadlers指Object、Array等数据的拦截handler
// collectionHandler是Map、Set等数据的拦截handler
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
// target[ReactiveFlags.RAW]指target为响应式
// 如果是响应式则直接返回
// 但是如果是创建Readonly类型数据,但是target是Reactive则需要重新创建
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
// 区分readonly与reactive
const reactiveFlag = isReadonly
? ReactiveFlags.READONLY
: ReactiveFlags.REACTIVE
// 如果原始数据已经被监听了,则返回对应响应类型数据
if (hasOwn(target, reactiveFlag)) {
return target[reactiveFlag]
}
// only a whitelist of value types can be observed.
// 不能被监听则直接返回
if (!canObserve(target)) {
return target
}
// 创建代理,其中核心就是handlers,下章effect中进行讲解
// cllectionTypes指Set、Map、WeakSet、WeakMap这种方式本身只支持get方法
// 其中为了使这些类型都可以监听到,使用了代理模式进行监听
const observed = new Proxy(
target,
collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
)
// 为原始数据添加reactiveFlag属性指向响应式数据
def(target, reactiveFlag, observed)
// 返回监听属性
return observed
}
可以看到createReactiveObject内部同样对Map、Set、WeakMap、WeakSet进行了监听,这样前端就可以快乐的使用Map、Set等这些类型变量进行响应式编程了,从而避免了前端一切皆对象的思想。Map与WeakMap的前端作用主要基于其Hash特性做缓存机制,这样就可以直接使用到Dom上,并且基于其Key可以当对象特性,前端的表单更易于实现,表单每项的文字可以包含更多内容。
细心的同学注意到了没有,这个reactive函数居然没有使用递归遍历整个变量进行监听,那是怎么进行监控整个对象的呢?答案就在handler中的get监听函数内部,get函数内部如果得到对应响应数据的值value,则会返回reactive(value),举个例子
const obj = reactive({ a: { b: 1 } });
此时只是针对obj做了响应式,而a是没有做响应是的,但是a中的getter拦截函数做了这件事情
getter(target, key) {
return reactive(target[key]);
}
访问obj.a.b的过程中,首先访问obj.a触发getter,返回reactive(obj[‘a’]),此时obj.a变成响应式数据结构,然后访问obj.a.b,此时触发obj.a.b触发getter,返回reactive(obj[‘a’][‘b’])。
现在懂了吧,vue3的另一个优化是延迟创建响应式结构,只有在访问的时候才会进行对下一级创建响应式,这样做的目的有两个:
1.延迟创建初始化对性能是有提升的,避免递归创建响应式影响创建时间,但是也消耗了访问时间,因为实时创建。
2.避免循环依赖,避免递归创建响应式造成循环依赖
1.ref相对于reactive,一个负责简单数据类型,一个负责复杂数据类型
2.readonly与reactive,一个负责只读数据类型,一个负责可读可写数据类型
3.reactive中针对响应式创建采用延迟创建方法,优化了性能
4.reactive针对Map、Set等数据结构支持响应式
vue3源码可以看出就是hook编码风格,开发人员拥有更大的主动性,可以依赖这些hook进行随机搭配使用,这样就可以使性能达到最大化。并且支持响应式的数据结构类型更多,响应这块代码做了优化,并且内部包含大量的Set与Map等新数据结构,使代码逻辑相对于之前更加简单。