vue3源码reactive章节

Target类型及属性


// 定义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的状态,并且通过一些判断函数来看出属性的作用。

canObserve

// 判断变量类型是否可以监控
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)
  )
}

是否可以监控的判断比较简单,主要从是否标记为原始类型、是否是可监控类型,是否冻结的几个维度进行判断是否可以监控。

Reactive

// 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做一个提问,看下有没有人有类似问题。

Readonly

// 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重新递归获取类型。

createReactiveObject

注意一下:
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等新数据结构,使代码逻辑相对于之前更加简单。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值