解析Vue3源码(一)——reactive
前言
不同于Vue2,Vue3声明响应式数据不是在$options.data()中声明而是需手动创建。区别如下
vue2
data: {
obj: {}
}
vue3
let obj = reactive({});
reactive的源码
function reactive(target) {
// 不监听只读数据,直接返回
if (isReadonly(target)) {
return target;
}
// 返回代理对象
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
// Proxy的handler
const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys
}
//
const get = /*#__PURE__*/ createGetter();
//
const set = /*#__PURE__*/ createSetter();
const mutableCollectionHandlers = {
get: /*#__PURE__*/ createInstrumentationGetter(false, false)
};
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
//非对象类型的数据直接返回
if (!isObject(target)) {
if ((process.env.NODE_ENV !== 'production')) {
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
if (target["__v_raw" /* RAW */] &&
!(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
return target;
}
// 目标对象已经被代理过直接返回缓存的代理对象。
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// 判断对象的类型,非指定类型不监听直接返回
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {
return target;
}
//创建代理对象并返回
//对象类型和集合类型的handler不一样
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}
如果reactive的内容不是对象则直接返回该数据,如果是对象,判断是否已经有代理对象,有直接返回。再判断对象的类型,只有Object, Array, Map, Set, WeakMap, WeakSet
才能被代理。根据代理对象类型是 1(Object, Array
)还是 2 来根据不同的handler生成代理对象并返回。并在已代理集合(proxyMap
)中加入该代理对象 proxyMap.set(target, proxy);
下面再看看代理对象的handler拦截的get方法内容:
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
//获取代理对象是否被代理过的标志位
if (key === "__v_isReactive" /* IS_REACTIVE */) {
return !isReadonly;
}
//获取代理对象是否只读的标志位
else if (key === "__v_isReadonly" /* IS_READONLY */) {
return isReadonly;
}
//获取代理对象是否是浅响应数据的标志位
else if (key === "__v_isShallow" /* IS_SHALLOW */) {
return shallow;
}
//获取原始数据
else if (key === "__v_raw" /* RAW */ &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap).get(target)) {
return target;
}
const targetIsArray = isArray(target);
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
const res = Reflect.get(target, key, receiver);
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res;
}
if (!isReadonly) {
track(target, "get" /* GET */, key);
}
if (shallow) {
return res;
}
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value;
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
如果获取的是内置的几个标志位属性即返回对应值,只读属性或浅层响应数据直接返回,如果是数组有指定的处理函数,如果是对象,track(target, “get”, key)进行依赖收集
以下是set方法拦截的实现
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
let oldValue = target[key];
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false;
}
if (!shallow && !isReadonly(value)) {
if (!isShallow(value)) {
value = toRaw(value);
oldValue = toRaw(oldValue);
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
}
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
//如果目标是在原始原型链上的东西,就不要触发
if (target === toRaw(receiver)) {
//
if (!hadKey) {
trigger(target, "add" /* ADD */, key, value);
}
else if (hasChanged(value, oldValue)) {
trigger(target, "set" /* SET */, key, value, oldValue);
}
}
return result;
};
}
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
如果是只读或者ref类型的数据,set操作不赋值,如果是浅层响应,直接赋值,否则再使用Reflect进行set操作,并判断新值与旧值是否相等,不等则执行trigger通知更新。
总结
在定义响应式对象时可以使用reactive,但是对于基础类型的数据,是无法使用reactive来监听其变化的,这就要用到ref
了,下面一篇就来讨论ref
的实现原理及使用