在 Vue3 里面是通过 ref()
与 reactive()
这两个 api 来创建响应式数据的。
官方文档上写着 ref()
这个方法允许我们创建可以使用任何值类型的响应式ref, ref() 会把传入的参数包装成一个带 .value
属性的对象。
-
一般来说
ref()
可以用来将String
、Number
、Boolean
、Null
等原始类型定义成一个响应式对象。 -
而
reactive()
则用来将复杂类型数据定义成一个响应式对象,如对象
、Map
等。
reactive
官方对于 reactive 函数相关的代码放在了core/packages/reactivity/src/reactive.ts
这个文件夹里面,点击此处跳转至源码。
在这个文件夹里面有四个响应式的核心方法:
- reactive(): 会将对象进行深层代理
- shallowReactive(): 浅层拷贝,只会对对象的第一层进行代理
- readonly(): 接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
- shallowReadonly: 除了第一层只可读,后面的就可以访问改变,也就是浅层只读。
以下是我在源码找到的部分方法,当然跟源码有点不一样哈。单看可以分为两类:第一类是只读类(readonly
);第二类是深层类(reactive
)。这几个方法都是依靠 createReactiveObject()
这个核心方法将 target 变成响应式对象的。
export function reactive(target){
return createReactiveObject(target,false,mutableHandlers)
}
export function shallowReactive(target){
return createReactiveObject(target,false,shallowReactiveHandlers)
}
export function readonly(target){
return createReactiveObject(target,true,readonlyHandlers)
}
export function shallowReadonly(target){
return createReactiveObject(target,true,shallowReadonlyHandlers)
}
再来细看源码中关于 reactive
函数的定义:
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) { // 如果传入的对象是只读的,那么直接返回该个只读对象
return target
}
return createReactiveObject( // 实现代理的核心
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
源码中 reactive 做的事情就是判断传入的对象是否为只读,如果是只读的对象就不进行代理。然后就是调用 createReactiveObject()
这个方法实现将传入的对象转换成响应式对象,这个方法也就是实现响应式代理的核心。
现在来看看实现响应式代理的核心方法:createReactiveObject()
function createReactiveObject(
target: Target, // 传入的目标
isReadonly: boolean, // 是否只读
baseHandlers: ProxyHandler<any>, // 实现代理的核心
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any> // 存储 target 对象的
) {
if (!isObject(target)) { // 先判断 reactive 代理的是不是一个对象,因为proxy只能代理一个对象
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// 优化:如果目标数据已经被代理了,直接返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// proxyMap 中已经存入过 target,直接返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 只有特定类型的值才能被 observe.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 如果上面的条件都不满足就通过 proxy 来代理一个响应式对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
创建reactive对象的函数的一个大概流程就是先判断目标是否为一个对象,否则 Proxy 就不能进行代理;然后判断目标是否已经被代理过,即:
target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
如果目标值的 Flags
是 RAW
(我觉得这个标签就是给对象的响应式标签),也就是该 target
不是响应式,并且 target
也不是已经代理过的,否则就直接返回。
然后就是判断 proxyMap
中是否已经存入过 target
:
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
再然后就是判断特定类型 getTargetType()
:
function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
这个函数看着有点懵…
然后不满足上面的条件就会创建一个 Proxy
对象,对 target
进行代理。
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
Proxy
是根据 targetType
来确定执行的是 collectionHandlers代理处理程序
还是 baseHandlers基础处理程序
collectionHandlers
是将集合、数组转换成代理对象的一些程序。baseHandlers
则是将对象转换成代理对象的一些程序。
简单看看collectionHandlers
,代码在 core/packages/reactivity/src/collectionHandlers.ts
目录
type IterableCollections = Map<any, any> | Set<any>
type WeakCollections = WeakMap<any, any> | WeakSet<any>
type MapTypes = Map<any, any> | WeakMap<any, any>
type SetTypes = Set<any> | WeakSet<any>
function get(){}
function has(){}
function size(){}
...
不难看出这是对于一些集合、数组进行处理的函数。
对于 baseHandlers
,代码在 core/packages/reactivity/src/baseHandlers.ts
目录,当然官方有更详细的介绍 BaseHandlers
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
创建完 Proxy 对象之后就是将对象存放到proxymap
中,再将 proxy
对象返回出来。然后针对四个方法,通过在createReactiveObject
里面传入不同的参数,封装出 reactive
、shallowReactive
、readonly
、shallowReadonly
样式的对象。实现对象的一个响应式代理。
本文正在参加「金石计划」
原文链接:https://juejin.cn/post/7217795738691977253 作者:布鲁斯要蓝调