前言
本篇文章的主要目的是搞懂响应式的实现原理。因此,关于其代码的细节部分,不会过多研究,仅以其核心代码为主。
相关文章:
Git 仓库:
准备
由于 vue3 中的各个模块都要要用到 @vue/shared 中的公共方法。所以,我们先把它里面的文件复制到自己的项目中,以便后面使用。
另外,vue3 实现响应式的方法与 vue2 不同,它使用的是 Proxy,而不是 Object.definedProperty。
如果你还不了解,建议先去学习一下。同时,还要了解一下 Reflect,因为它们总是会同时使用。
createReactiveObject
想必大家都已了解 vue3 中的 reactive、shallowReactive、readonly、shallowReadonly 函数。
这些函数,虽然功能有所不同,但都会返回一个对象的代理。但这个代理,实际上是由一个叫 createReactiveObject 的函数创建的。就像下面这样。
// reactivity/src/reactive.ts
export function reactive(target) {
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
export function shallowReactive(target) {
return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap);
}
export function readonly(target) {
return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap);
}
export function shallowReadonly(target) {
return createReactiveObject(target, true, shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap);
}
reactive 等函数,在创建对象的代理时,会将它们各自的参数传给 createReactiveObject 函数,这样就可以根据参数的不同,来实现其相应的功能。
代码分析
从上述代码中,我们已知道,createReactiveObject 才是创建响应式代理的核心函数。下面,我们来分析一下这个函数。
参数
createReactiveObject 有五个参数:
-
target,需要被代理的对象。 -
isReadonly,一个Boolean,用于判断已被代理过的对象,是不是又被readonly或shallowReadonly进行了代理。 -
baseHandlers传给Proxy,作为其第二参数,即handler。 -
collectionHandlers传给Proxy,作为其第二参数,即handler。 -
proxyMap:用来缓存已被代理过的
target,避免对同一个target重复代理。
注意,baseHandlers 和 collectionHandlers 这两个参数,都是作为 Proxy 的 handler,但它们不会同时存在,而是根据 对象的类型 进行选择。
例如:
- 对象的类型是
'[object Object]'、'[object Array]',选baseHandlers。 - 对象的类型是
'[object Map]'、'[object Set]'、'[object WeakMap]'、'[object WeakSet]',选collectionHandlers。
返回值
createReactiveObject 最终会返回一个对象的代理,即 Proxy。
细节问题
createReactiveObject 创建响应式的代理,需要注意以下问题:
- 需要代理的
target,其数据类型必须是对象 target已经是 Proxy 对象,则无需再代理
let data = reactive(obj);
let data2 = reactive(data); // 传入代理对象 data,则不会再次代理
- 同一个
target,无需重复代理
let data = reactive(obj);
let data2 = reactive(obj); // 再次传入 obj,则不会再代理
- 对象的类型必须是
'[object Object]'、'[object Array]'、'[object Map]'、'[object Set]'、'[object WeakMap]'、'[object WeakSet]'其中之一,否则直接返回。
代码实现
在上文中,我们了解到, createReactiveObject 有五个参数。但在本文的代码讲解中,为求简便,在创建对象的代理时,将省略对 collectionHandlers 这一参数的处理。
换句话说,就是本文中的代码只考虑为对象类型是 '[object Object]' 和 '[object Array]' 的对象创建代理。
创建 Proxy 对象
在 reactivity / src / reactive.ts 文件中,编写创建响应式代理的相关代码。
// reactivity/src/reactive.ts
import {
mutableHandlers,
readonlyHandlers,
shallowReactiveHandlers,
shallowReadonlyHandlers
} from './baseHandlers'
import { isObject, toRawType } from '@vue/shared';
// 关于响应式的枚举对象
export const enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive',
RAW = '__v_raw'
}
// `reactive`、`shallowReactive`、`readonly`、`shallowReadonly` 各自的 map,用于缓存它们各自创建的 target 的 Proxy 对象,避免对其重复代理
export const reactiveMap = new WeakMap();
export const shallowReactiveMap = new WeakMap();
export const readonlyMap = new WeakMap();
export const shallowReadonlyMap = new WeakMap();
const enum TargetType {
INVALID = 0,
COMMON = 1,
COLLECTION = 2,
}
// 对象类型
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON;
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION;
default:
return TargetType.INVALID;
}
}
// 获取 value 的类型
function getTargetType(value) {
return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));
}
export function reactive(target) {
return createReactiveObject(target, false, mutableHandlers, reactiveMap);
}
export function shallowReactive(target) {
return createReactiveObject(target, false, shallowReactiveHandlers, shallowReactiveMap);
}
export function readonly(target) {
return createReactiveObject(target, true, readonlyHandlers, readonlyMap);
}
export function shallowReadonly(target) {
return createReactiveObject(target, true, shallowReadonlyHandlers, shallowReadonlyMap);
}
// 创建对象代理的核心函数
function createReactiveObject(target, isReadonly, baseHandlers, proxyMap) {
// 若 target 不是对象,则直接返回
if (!isObject(target)) {
console.warn(`参数必须是对象`);
return target;
}
// 已经是 Proxy 对象,则直接返回
// 异常处理: 在响应式对象上调用 readonly()
if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
console.warn(`已经是代理对象`);
return target;
}
// 若 target 已经代理过,则无需重复代理。用于避免同一个普通对象被多次代理。
const exisitingProxy = proxyMap.get(target);
if (exisitingProxy) {
console.warn(`已被代理过`);
return exisitingProxy;
}
// 只观察特定的值类型,例如:'Object'、'Array'、'Map'、'Set'、'WeakMap'、'WeakSet'
const targetType = getTargetType(target);
if (targetType === TargetType.INVALID) {
console.warn('类型有误');
return target;
}
// 通过 Proxy 对 target 进行代理。代理对象进行取值触发 get,赋值则触发 set
const proxy = new Proxy(target, baseHandlers);
// 存储已被代理过的对象
proxyMap.set(target, proxy);
// 返回代理对象
return proxy;
}
handler
新建 reactivity / src / baseHandlers.ts。此文件模块是为 '[object Array]' 和 '[object Object]' 这样的对象定义 handler。
reactive、shallowReactive、readonly、shallowReadonly 都有各自的handler,主要区别在于它们的 get 和 set 函数。
因此,就定义了 createGetter 和 createSetter 函数,用以创建 get 和 set 函数。
// reactivity/src/baseHandlers.ts
import {
reactive,
readonly,
toRaw,
ReactiveFlags,
readonlyMap,
reactiveMap,
shallowReactiveMap,
shallowReadonlyMap,
isReadonly,
isShallow
} from './reactive';
import {
isObject,
extend,
isArray,
hasOwn,
isIntegerKey,
hasChanged
} from '@vue/shared';
// `reactive`、`shallowReactive`、`readonly`、`shallowReadonly` 各自的 get 函数
const get = createGetter();
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true);
const shallowReadonlyGet = createGetter(true, true);
// 判断对象自身属性中是否具有指定的属性
function hasOwnProperty(key) {
const obj = toRaw(this);
return obj.hasOwnProperty(key);
}
// 创建 get 函数
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
// 调用 isReactive() 触发,或在响应式对象上调用了 readonly()
return !isReadonly;
} else if (key === ReactiveFlags.IS_READONLY) {
// 调用 isReadonly() 触发
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
// 调用 isShallow() 触发
return shallow
} else if (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
// 若是条件为真,则说明 target 已被代理过。receiver 是 target 的代理对象(proxy)。
return target;
}
if (!isReadonly) {
// hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
// Reflect.get 方法,允许你从一个对象中取属性值。
const res = Reflect.get(target, key, receiver);
// shallow 真,则说明是一个浅层响应式对象,它只有根级别的属性是响应式的。
if (shallow) {
return res
}
// 深度代理对象。取值时,判断访问的属性值,是否为对象,若是,则对其进行代理。
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res;
};
}
// `reactive`、`shallowReactive`、`readonly`、`shallowReadonly` 各自的 set 函数
const set = createSetter();
const shallowSet = createSetter(true);
// 创建 set 函数
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
let oldValue = target[key];
// 排除浅模式
if (!shallow) {
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue);
value = toRaw(value);
}
} else {
// 在浅模式下,不管是否响应,对象都被设置为原样
}
// Reflect.set 方法允许你在对象上设置属性。它返回一个 Boolean 值表明是否成功设置属性。
const result = Reflect.set(target, key, value, receiver);
return result;
};
}
// 拦截对对象属性的 delete 操作
function deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
return result;
}
// handler.has() 方法是针对 in 操作符的代理方法。检查目标对象上是否存在key属性
function has(target, key) {
const result = Reflect.has(target, key);
return result;
}
// 用于拦截 Reflect.ownKeys(),返回一个可枚举对象
function ownKeys(target) {
return Reflect.ownKeys(target);
}
// 导出 reactive 创建对象的代理时,所需要的 handler
export const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys
};
// 导出 readonly 创建对象的代理时,所需要的 handler
export const readonlyHandlers = {
get: readonlyGet,
set(target, key) {
// 不能为 只读对象 设置 key。只需返回 true,什么也不做。
console.warn(
`[Vue warn]: Set operation on key "${String(key)}" failed: target is readonly.`,
target
);
return true;
},
deleteProperty(target, key) {
// 不能删除 只读对象 的 key
console.warn(
`[Vue warn]: Delete operation on key "${String(key)}" failed: target is readonly.`,
target
);
return true;
}
};
// 导出 shallowReactive 创建对象的代理时,所需要的 handler
export const shallowReactiveHandlers = extend({}, mutableHandlers, {
get: shallowGet,
set: shallowSet
});
// 导出 shallowReadonly 创建对象的代理时,所需要的 handler
export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
get: shallowReadonlyGet
});
导出函数
在 reactivity / src / index.ts 文件中,导出 reactive 等函数。
// reactivity/src/index.ts
export {
reactive,
readonly,
shallowReactive,
shallowReadonly
} from './reactive';
上述代码完成后,我们就可以使用 reactive 等函数,来创建对象的代理了。下面,我们来讲解一下它的细节问题。
细节问题解析
上文,我们介绍了 createReactiveObject 创建代理对象时,所需要注意的四个问题。下面,我们来分析一下它们的代码实现。
问题-1
使用 isObject 来判断 target 是不是对象,若不是,则直接返回它。换言之,reactive、shallowReactive、readonly、shallowReadonly 接收的参数,其类型必须是对象。
// 若 target 不是对象,则直接返回
if (!isObject(target)) {
console.warn(`参数必须是对象`);
return target;
}
问题-2
通过枚举对象 ReactiveFlags 的 RAW 和 IS_REACTIVE 属性,判断 target 是否已经是代理对象。这需要在 createReactiveObject 和 createGetter 这个两个函数中,写入相应的判断条件。
createReactiveObject中的判断条件。
// 已经是 Proxy 对象,则直接返回
// 异常处理: 在响应式对象上调用 readonly()
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
console.warn(`已经是代理对象`);
return target;
}
createGetter中的判断条件
if (key === ReactiveFlags.IS_REACTIVE) {
// 调用 isReactive 时触发此条件,或在响应式对象上调用了 readonly()
return !isReadonly;
} else if (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
// 若是条件为真,则说明 target 已被代理过。receiver 是 target 的代理对象(proxy)。
return target;
}
下面,我们来讲解一下,这两个判断条件,是如何防止已代理的对象被再次代理的:
-
当
target是已被代理过的对象。然后,我们把它作为参数,传给reactive函数。 -
当代码执行到
if (target[ReactiveFlags.RAW])时,target会进行取值操作,而取值,则必定触发target的get方法,这是因为被代理过的对象都有get方法。 -
既然触发了
get,那么在get中获取到的key就会是__v_raw,所以key === ReactiveFlags.RAW必然为真,然后返回target,即得到一个真值。 -
target[ReactiveFlags.IS_REACTIVE]的触发原理也是一样,若是!(isReadonly && target[ReactiveFlags.IS_REACTIVE])最终值为true,那么整个条件为真,就会返回target,不再继续执行,从而避免再次对已代理过的对象进行代理。
问题-3
定义 reactive、shallowReactive、readonly、shallowReadonly 各自的 map,用于缓存它们各自创建的 target 的 Proxy 对象,避免对其重复代理。
export const reactiveMap = new WeakMap();
export const shallowReactiveMap = new WeakMap();
export const readonlyMap = new WeakMap();
export const shallowReadonlyMap = new WeakMap();
在每一次创建代理对象前,先判断一下当前的 target 是不是已被代理过,又再次被代理的。
如果是,就获取其对应的值并返回 proxyMap.get(target)。
// 若 target 已经代理过,则无需重复代理。用于避免同一个普通对象被多次代理。
const exisitingProxy = proxyMap.get(target);
if (exisitingProxy) {
console.warn(`已被代理过`);
return exisitingProxy;
}
如果不是,就创建其代理对象,然后把它保存到 proxyMap 中。
// 存储已被代理过的对象
proxyMap.set(target, proxy);
另外,使用原生的 WeakMap,是因为其持有的是每个键对象的 弱引用,这意味着在没有其他引用存在时垃圾回收能正确进行。
问题-4
将对象的类型分为三类,并用数字枚举为每个类型匹配一个与之对应的数值
- 第一类:
Object、Array,对应值1 - 第二类:
Map、Set、WeakMap、WeakSet,对应值2 - 第三类:其它,例如:
Date,对应值0
以上说的对象的类型,可通过 Object.prototype.toString.call() 得到。
// reactivity/src/reactive.ts
const enum TargetType {
INVALID = 0,
COMMON = 1,
COLLECTION = 2,
}
function targetTypeMap(rawType) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON;
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION;
default:
return TargetType.INVALID;
}
}
定义 getTargetType函数,根据对象的类型,获取其相应的值。
function getTargetType(value) {
return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));
}
Object.isExtensible() 判断一个对象是否可以扩展。
例如,用 Object.seal 密封的对象,是就不能再扩展的。对于这种情况,可直接返回 TargetType.INVALID。
而 toRawType 是 shared 模块中的一个方法。它可以把 "RawType",从像 "[object RawType]"这样的字符串中提取出来。
例如, 用 Object.prototype.toString.call(Array) 得到 "[object Array]" 字符串,我们将它传入 toRawType,就可以获得字符串 "Array".
根据所得对象的类型,进行判断。
// 对象的类型必须是 'Object'、'Array'、'Map'、'Set'、'WeakMap'、'WeakSet' 其中之一 ,否则直接返回
const targetType = getTargetType(target);
if (targetType === TargetType.INVALID) {
return target;
}
测试
执行 pnpm run dev 命令进行打包,然后在 html 中引入 reactivity.global.js,进行测试。下面是测试案例。
const { reactive } = VueReactivity;
const obj = { name: 'demo', age: 18, info: { title: '信息'} };
// 测试:参数必须是对象
//const data = reactive(20);
// 测试:深度代理
// const data = reactive(obj);
// console.log(data.info);
// 测试:重复代理
// const data = reactive(obj);
// const data2 = reactive(obj);
// 测试:已经是代理对象
// const data = reactive(obj);
// const data2 = reactive(data);
// 测试:取值、赋值
const data = reactive(obj);
console.log(data.name); // 取值
data.name = '哈哈'; // 赋值
setTimeout(() => {
console.log(data.name);
}, 1000);
关于数组的问题
同 Object.defineProperty 相比,虽然 Proxy 能够直接对数组进行代理,但是在使用数组的某些方法(例如:indexOf、includes 等方法)时,无法避免对其长度的跟踪,甚至在某些情况下还会导致无限循环。所以,仍需要像 vue2 一样,对这些数组方法进行重写。
vue3 源码,重写了 8 种数组方法,可分为两种类型:
- 不会改变数组的方法:
includes,indexOf,lastIndexOf - 会改变数组的方法:
push,pop,shift,unshift,splice
为了更好地理解,下面我们用打包好的 reactivity.global.js 进行演示。
不会改变数组的方法
includes, indexOf, lastIndexOf 这些方法仅是获取数组某个值的下标值或是判断其是否存在,而不会改变数组。因此,它们只会触发数组的代理对象的 get 函数。
测试案例:
const { reactive } = VueReactivity;
const data = reactive(['js', 'node']);
// 使用 indexOf 测试
data.indexOf('js');
测试结果:
当调用 indexOf 访问数组中 js 的下标值时,就会触发 get,但触发了多次。然而,我们仅需要 key 为 indexOf 这一次。
会改变数组的方法
push, pop, shift, unshift, splice 这些方法会改变数组,它们会触发数组的代理对象的 get 和 set 函数。
测试案例:
const { reactive } = VueReactivity;
const data = reactive(['js', 'node']);
// 使用 unshift 测试
data.unshift('html');
测试结果:
当调用 unshift 这种会改变数组的方法时,即访问了数组,也对其做出了修改。所以,它会触发 get 和 set。虽然,这两个函数都被触发了多次,但就 get 而言,我们只需要 key 为 unshift 这一次。
重写数组方法
- 添加
createArrayInstrumentations方法,实现对上述 8 种数组方法的重写。
// reactivity / src / baseHandler.ts
// 存放重写后的数组方法的对象
const arrayInstrumentations = createArrayInstrumentations();
// 重写数组方法,在实现上,内部依旧是通过原生的数组方法来调用,只是要做一些改变。
function createArrayInstrumentations() {
const instrumentations = {};
['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
// this -> 原数组的代理对象,args -> 传入数组方法中的参数
instrumentations[key] = function (this, ...args) {
// 通过 toRaw 返回原数组,避免死循环导致栈溢出
const arr = toRaw(this);
// 通过数组的原生方法调用,倘若参数 args 是响应式的,则要用 toRaw 将其还原为原始对象,然后再调用。
const res = arr[key](...args);
// 'includes' 如果找到匹配的字符串则返回 true,否则返回 false
// 'indexOf' 和 'lastIndexOf' 如果没有找到匹配的字符串则返回 -1
if (res === -1 || res === false) {
return arr[key](...args.map(toRaw));
} else {
return res;
}
};
});
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => {
instrumentations[key] = function (this, ...args) {
const arr = toRaw(this);
// 通过数组的原生方法调用,并通过 apply 方法,防止this 指向改变
const res = arr[key].apply(this, args);
return res;
};
});
return instrumentations;
}
- 修改
createGetter函数,添加对数组的处理代码。
// reactivity / src / baseHandler.ts
function createGetter() {
return function get(target, key, receiver) {
// 略...
// 判断 target 是否为数组,且 key 存在于 arrayInstrumentations 上
const targetIsArray = isArray(target);
if (!isReadonly) {
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
console.log(`数组-get-操作: key->${key}`);
return Reflect.get(arrayInstrumentations, key, receiver);
}
// hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
// 略...
};
}
将上述代码添加到 const res = Reflect.get(target, key, receiver) 代码的前面。
- 修改
createSetter函数,添加对数组的处理代码。
// reactivity / src / baseHandler.ts
function createSetter() {
return function set(target, key, value, receiver) {
let oldValue = target[key];
// 排除浅模式
if (!shallow) {
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue);
value = toRaw(value);
}
} else {
// 在浅模式下,不管是否响应,对象都被设置为原样
}
// isArray 和 isIntegerKey 方法,是用来判断当前操作的对象是否为数组
// Number(key) < target.length,为 真,则是对数组中的某个值进行修改,否则就是对数组执行新增,即添加值
// hasOwn(target, key),对象上是否存在某个属性,为真,则是修改,否则就是新增
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
// Reflect.set 方法允许你在对象上设置属性。它返回一个 Boolean 值表明是否成功设置属性。
const result = Reflect.set(target, key, value, receiver);
// 是同一个对象,才能执行触发操作
if (target === toRaw(receiver)) {
// hadKey 为 false,表示当前对象执行新增操作,否则就是在执行修改操作。
// 这样判断,是为了区分对象执行的操作(新增或修改),以便明确如何执行触发。
if (!hadKey) {
console.log(`set-新增操作: key->${key}, hadKey->${hadKey}`);
} else if (hasChanged(value, oldValue)) {
console.log(`set-修改操作: key->${key}, hadKey->${hadKey}`);
}
}
return result;
};
}
注意,target === toRaw(receiver) 这段代码,只有当其为真,才能执行触发操作。下面,介绍一下这样做的缘由。
receiver 指的是最初被调用的对象。通常是 proxy 本身,但 handler 的 set 方法也有可能在原型链上,或以其他方式被间接地调用。换句话说,也不一定就是 proxy 本身。
看下面这个例子:
let p = new Proxy({}, {
set: function (target, key, value, receiver) {
console.log(`target:`, target);
console.log(`receiver:`, receiver);
return Reflect.set(target, key, value, receiver);
}
});
// Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
let obj = Object.create(p);
obj.name = 'jen';
执行 obj.name = 'jen', obj 不是一个 proxy,且自身不含 name 属性,但是它的原型链上有一个 proxy,那么,这个 proxy 的 set() 处理器会被调用,而此时,obj 会作为 receiver 参数传进来。
那么,就会出现 receiver 不是 proxy 本身的情况。
所以,我们需要通过 target === toRaw(receiver) 来判断是不是同一个对象,即为 true 时,才执行触发操作。
另外,触发操作,分两种情况:新增 和 修改。
当hadKey 值为 false 时,表示对象执行新增操作,否则就是在执行修改操作。因此,我们可以根据 hadKey 来判断如何执行触发操作。
注意,这里所说的触发,是指和依赖收集相关的操作。但,在目前的代码中,只是加以区分,并未做相应处理。
测试案例:
const { reactive } = VueReactivity;
const arr = ['js', 'node'];
const data = reactive(arr);
data.unshift('html')
console.log(data, '结果');
测试结果:
toRaw
toRaw() 返回由 reactive()、readonly() shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。
// reactivity/src/reactive.ts
// 返回代理对象的原始对象
export function toRaw(observed) {
const raw = observed && observed[ReactiveFlags.RAW];
return raw ? toRaw(raw) : observed;
}
observed[ReactiveFlags.RAW] 的触发原理,可参考上文 问题2 中做出的解析。
isReadonly
检查传入的值是否为只读对象。
// reactivity/src/reactive.ts
export function isReadonly(value) {
return !!(value && value[ReactiveFlags.IS_READONLY]);
}
value[ReactiveFlags.IS_READONLY] 的触发原理,可参考上文 问题2 中做出的解析。
isReactive
检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
// reactivity/src/reactive.ts
export function isReactive(value) {
if (isReadonly(value)) {
return isReactive(value[ReactiveFlags.RAW]);
}
return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}
value[ReactiveFlags.RAW] 和 value[ReactiveFlags.IS_REACTIVE] 的触发原理,可参考上文 问题2 中做出的解析。
isShallow
检查一个对象是否是由 shallowReactive() 或 shallowReadonly() 创建的代理。
// reactivity/src/reactive.ts
export function isShallow(value) {
return !!(value && value[ReactiveFlags.IS_SHALLOW]);
}
value[ReactiveFlags.IS_SHALLOW] 的触发原理,可参考上文 问题2 中做出的解析。
结束
本篇文章中的代码及其变量名都是根据源码来写的,希望对想要深入研究的同学们,能有所帮助。