Vue3中的reactive与readonly
前言
本文主要整理关于在
vue3
中所提及到的所有与reactive
以及readonly
相关的API,通过对比每个API的作用以及使用场景,新增对vue3
中相关的API的认知,主要借助于vue3官方API的阅读!
ES2015的Proxy与Reflect
在开始学习关于
vue3
的reactive
之前,先来了解一下关于什么是Proxy
以及Reflect
,Proxy
与Reflect
都是ES6中引入的新特性,它们通常一起使用以提供更灵活和强大的对象操作能力!
Proxy
Proxy
用于修改对象某些操作的默认行为,等同于在语言层面对这个默认行为做出的修改,属于一种“元编程(编程的编程)”,可以理解为在目标对象做出默认响应(如属性查找、赋值、枚举、函数调用等等)之前提供一到“拦截”动作,外界对该对象默认行为的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy
原意是代理,用于表示用它来“代理”某些操作,可以简单理解为“代理器”,语法形式如下:
1 | const p = new Proxy(target, handler) |
🤩 参数说明:
- target: 要使用
Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组、函数,甚至另外一个代理); - handler: 一个普通对象,其成员一般都是函数类型的,函数分别定义了在执行对象的默认操作时,将提供的对应的拦截动作;
关于在handler
对象中所提供的相关属性可以有 👇 的成员:
- getPrototypeOf: Object.getProtptypeOf方法的拦截器;
- setPrototypeOf: Object.setPrototypeOf方法的拦截器;
- isExtensible: Object.isExtensible方法的拦截器;
- preventExtensions: Object.preventExtensions方法的拦截器;
- getOwnPropertyDescriptor: Object.getOwnPropertyDescriptor方法的拦截器;
- defineProperty: Object.defineProperty方法的拦截器;
- has: in操作符的拦截器;
- get: 对象属性读取操作的拦截器;
- set: 对象属性赋值操作的拦截器;
- deleteProperty: delete操作符的拦截器;
- ownKeys: Object.getOwnPropertyNames方法和Object.getOwnPropertySymbols的拦截器;
- apply: 函数调用操作的拦截器
- construct: new操作符的拦截器;
比如有 👇 的一个例子:
1 | const handler = { |
👆 上述例子中handler
重新定义了对象的get
动作,当从一个对象中获取属性时,如果属性存在则返回对应的值,不存在则返回默认的一个37数字。然后在p2中没有定义任何拦截操作,则说明将不做任何的逻辑处理!
😕 关于这个Proxy
一般都有哪些应用场景呢? 👉 可以作为对象的验证器(通过重写set方法)、扩展构造函数(通过重写construct以及apply)等等!
Reflect
Reflect
是一个内置的对象,它提供了拦截Javascript
操作的方法,这些方法与上述的proxy_handler的方法一样,**Reflect
不是一个函数对象**,因此它不可构造!Reflect
的所有属性和方法都是静态的(就像Math一样)
以下是关于Reflect
的一个简单运用:
1 | const obj = { |
使用Reflext
可以让代码更加具有可读性和可维护性,因为它提供了一种统一的方式来调用基本操作,并且在操作失败时提供了更加友好的错误处理方式
reactive成员一览
vue3
中的相关reactive
成员的实现,则都是基于上述的Proxy
以及Reflect
来实现的!
对象响应式核心reactive()
reactive()
接收一个对象参数,返回这个对象的响应式代理,通过该API所创建出来的对象是一个Proxy
对象,一般只操作这个Proxy
对象,而不会去使用原始对象。
以下是对应的一个简单例子:
1 | const obj = reactive({ count: 0 }) |
😕 而这个过程发生了什么事呢,下面来简单分析一波关于这个API的实现(隐藏非关联的代码):
1 | // packages/reactivity/src/reactive.ts |
🌟 通过开篇关于Proxy
的使用我们可以得知,上述代码中的handler
,必须是一个普通的对象,其所提供的属性对应方法将拦截了target对象的默认操作!
在vue3
中,凭借typescript
,在其公共基础类型库中,通过自定义ProxyHandler<T extends object>
接口,将handler
中所提供的相关属性都罗列出来,为方便编写handler提供编写说明与提示:
1 | interface ProxyHandler<T extends object> { |
🌟 上述通过结合BaseReactiveHandler
以及MutableReactiveHandler
,最终实现对get
与set
方法的重写,来拦截相关的属性存取操作,通过在get
获取属性时对该key设置监听,然后在set
赋值属性时,触发监听动作,实现对对象属性的默认监听与拦截操作(这里是将触发对应的界面自动更新)!
😕 那么通过这个reactive()
方法所创建出来的Proxy
对象与其他对象有什么区别吗?
👆 从上面我们可以看出所创建出来的Proxy
对象拥有两个属性:target
+ handler
,这与我们开头所习得的知识一样,然后从handler
的继承关系来看,与源码的执行结果是一致的,继承关系如下图所示:
🤔 上述关于Target
类型是什么呢?为什么要设计这样子的一个类型?
👉 首先从上述的代码我们发现关于这个Target
就是一普通的对象,只不过它可能拥有着__v_skip
、__v_isReactive
、__v_isReadonly
、__v_isShallow
、__v_raw
这几个属性中的一个或者多个属性,在上述的createReactiveObject
方法中,我们用一句话:对象已经被代理了,来判断对象是否已被代理过,其对应的实现如下:
1 | if(target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])){ |
🌟 也就是说,通过reactive()
所创建出来的代理对象,可通过ReactiveFlags.RAW
以及ReactiveFlags.IS_REACTIVE
来访问其值!
😕 这里发现我们所传递的对象中是没有上面的这些属性的,但是通过Proxy
,我们发现在其handler的get
属性对应的方法中声明了对这些属性的访问:
1 | class BaseReactiveHandler implements ProxyHandler<Target>{ |
🌟 这里自定义了对__v_isReactive
、__v_isReadonly
、__v_isShallow
、__v_raw
这几个属性的访问,然后根据当前对象所采用的模式(通过reactive()
、readonly()
、shallowReactive()
三者之一),来提供对应的get
以及set
操作!而且,还在对应的handler中保留了通过__v_raw
的方式来访问原始对象(但这种方式尽量少用,而是使用toRaw
),关于这个toRaw
的方法定义如下:
1 | export function toRaw<T>(observed: T):T { |
🥹 与toRaw
相关的另外一个API是makeRaw
,关于此方法的定义如下
1 | export type Raw<T> = T & { [RawSymbol]?: true } |
🌟 从方法中我们可以看出此方法通过接收一对象,然后将其转化成为不可响应式的对象, 🤔 这里为什么添加了这个ReactiveFlags.SKIP
就可以认为它是一个不可响应的对象呢?
👉 我们在上述的代码中,利用伪代码对象非合法的类型
,来代替对一个对象是否可代理的检测,关于此伪代码的实现如下:
1 | function targetTypeMap(rawType: string) { |
🤩 通过调用
getTargetType(value)
获取到对象的类型,如果是TargetType.INVALID
的话,则说明是不可被响应式的对象!
shallowReactive()
reactive()
的浅层实现,一般比较少用,因为它创建的树具有不一致的响应行为,可能很难理解与调试
isShallow()
检查传入的对象是否是一个
shallow
对象,该方法定义如下:
1 | export function isShallow(value: unknown): boolean { |
isReactive()
检查一个对象是否由
reactive()
或者shallowReactive()
创建出来的代理对象,该方法定义如下:
1 | export function isReactive(value: unknown): boolean { |
isProxy()
检查一个对象是否由
reactive()
方法所创建出来的Proxy
对象,该方法定义如下:
1 | export function isProxy(value: any): boolean{ |
readonly成员一览
readonly()
接收一个对象或者一个ref,返回一个原value的只读代理,只读代理是深层的,对任何嵌套属性的访问都将是只读的!
关于此方法的实现如下:
1 | export function readonly<T extends object>( |
🌟 readonly()
的实现与reactive()
的实现类似,就是传递的参数不一样而已,主要在于这个readonlyHandlers
的实现不同,如下所示:
1 | class ReadonlyReactiveHandler extends BaseReactiveHandler { |
🌟 从上面的handler我们可以发现,readonly()
仅仅是实现了get方法,然后对set以及deleteProperty进行了直接返回,也就是不允许对对象的属性新型编辑操作!
isReadonly()
检查传入的值是否为只读对象,只读对象的属性可以更改,但他们不能通过传入的对象来直接赋值,这有点类似于
const obj
,而通过readonly()
以及shallowReadonly()
创建的代理都是只读的,因为它们在对应的代理对象的handler中将set方法给铲掉了!关于isReadonly()
方法定义如下:
1 | export function isReadonly(value: unknown): boolean { |
🌟 通过访问一个对象中的属性__v_is_readOnly
值是否为true来判断是否为一个readonly对象
shallowReadonly()
readonly()
的浅层作用形式
总结
关于上述对
reactive()
以及其关联的其他API进行一个分析之后,发现当我们通过const state = reactive({count: 1})
这个语句执行时,往被代理的对象中新增一系列自定义属性(__v_isXXX),代表正常响应式的、还是只读响应式的、还是浅层响应式的