Vue3 源码阅读(2)- reactive
首先我们来看 packages/reactivity/reactive.ts
是如何创建响应式对象的
reactive
对外暴露的最常用的接口
1 | export function reactive(target: object) { |
最表层的逻辑很简单,判断是否为只读对象
是则返回其本身
否则创建一个响应式对象返回
createReactiveObject
是创建响应式对象的工厂方法,是该文件的核心
createReactiveObject
接着再到创建响应式对象这个方法
这边我们拆解下逻辑慢慢看:
是否为对象类型
1 | if (!isObject(target)) { |
如果不是对象类型则返回对象本身
若在开发模式下,会在控制台打印警告
响应式系统处理是否处理过
如果 **目标被vue处理过,且当前调用的不是readonly()
**,直接返回其本身
1 | // target is already a Proxy, return it. |
也就是说下面这三种情况都会返回内层函数的值:
1 | // isReadonly = false, target[ReactiveFlags.IS_REACTIVE] = true |
readonly(reactive({}))
会继续向下执行,结果是多了一层 readonly
的代理,这可以在内部对象是响应式的情况下,确保其值不会被修改,同时符合只读的行为
如果直接返回 target
,那么 readonly(reactive{})
实际上是可以修改的
是否存在其对应的响应式代理对象
如果在 proxyMap
中找到了,直接返回找到的代理对象
1 | // target already has corresponding Proxy |
其中,proxyMap
是一个 WeakMap
,其键为原始对象的地址,值为对应的响应式代理对象
例如我们有下面代码:
1 | <script setup lang="ts"> |
foo
和 bar
是同一个对象
传入的 proxyMap
的作用也就很明显了,用于存储、复用 reactive
函数创建的响应式对象
判断对象类型
走到这,可以确认对象没有被 vue 处理过
判断下是不是 vue 可以处理的类型,如果不是,则返回原始值(INVALID
就是无效的意思)
1 | // only specific value types can be observed. |
getTargetType
函数请查阅本文其对应小节
创建响应式对象
使用了 Proxy
对象来进行监听对象赋值取值行为
接着把代理对象放到 proxyMap
中,最后返回
1 | const proxy = new Proxy( |
其它公开的 api
一些对外公开但是实现与 reactive
类同或者过于简单的 api
shallowReactive
也是用 createReactiveObject
工厂方法构建的:
1 | export function shallowReactive<T extends object>( |
Handlers
传入的有所不同,proxyMap
也不同
readonly
同上:
1 | export function readonly<T extends object>( |
调用 createReactiveObject
构建时 isReadonly
传了 true
shallowReadonly
同上:
1 | export function shallowReadonly<T extends object>(target: T): Readonly<T> { |
isReactive
用于判断是否为响应式的对象,文档中有提到:
如果该代理是
readonly
创建的,但包裹了由reactive
创建的另一个代理,它也会返回true
。
看看源码:
1 | export function isReactive(value: unknown): boolean { |
在判断为只读后,会获得其 RAW
即原始对象,判断其原始对象是不是响应式的;
例如:
1 | const objReact = reactive({ value: 0 }); |
isReactive
中获得到的 RAW
就是 objReact
,返回的也自然是 true
了
isReadonly
判断是否只读
1 | export function isReadonly(value: unknown): boolean { |
没啥好说的,判断 IS_READONLY
标记位
isShallow
判断是否浅层响应
实现同上
isProxy
判断是否为响应式系统中的对象
直接返回 isReactive
与 isReadonly
的结果的或运算
toRaw
递归调用获得其原始对象,用于处理例如 isReactive
中的情况
1 | export function toRaw<T>(observed: T): T { |
获得目标 RAW
标记位的值;
如果为空,则返回目标本身;
不为空则使用 RAW
的值递归调用
markRaw
标记一个响应式对象永远返回原始值,并且不再为响应式
1 | export function markRaw<T extends object>( |
使用工具方法,标记 SKIP
为 true
其它内部方法
简单说下较重要的实现
getTargetType
用于获取对象的类型
1 | function getTargetType(value: Target) { |
如果对象被标记跳过或者不可扩展(被 Object.freeze
冻结或者其它),返回不可用,否则返回 targetTypeMap
方法的返回值
toRawType
方法简单说下:
通过 Object().toString.call(obj)
获得类似 "[object Foo]"
的字符串,然后截取 "Foo"
返回
targetTypeMap
从这个方法可以知道 vue 能转换哪些对象为响应式对象
1 | function targetTypeMap(rawType: string) { |
这些类型分为两大类,COMMON
和 COLLECTION
,最基础的能直接访问的数据结构和需要用迭代器、函数来访问的数据结构
vue 中监听这两种对象的监听器实现有所不同