【vue3】响应式的几个重要API
创建响应式对象
RefImpl方式
ref(data)
使用这个构造函数RefImpl创建的响应式对象有一个属性: __v_isRef=true。如果data是原始类型属性_value=_rawValue,就是原始值。如果data是对象类型,那么就使用toReactive-->reactive(data) ,此时_value就是一个响应式对象。不会重复封装被ref()封装过的值,
function ref(value) {
return createRef(value, false);
}
//首先判断是不是Ref,如果是就直接返回,就是不做嵌套封装
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
//浅层代理,就是第一层属性
function shallowRef(value) {
return createRef(value, true);
}
/// ref构造函数
class RefImpl {
///__v_isShallow 是否是浅层代理,就是代理第一层
constructor(value, __v_isShallow) {
//__v_isShallow: 标记是否为浅层代理(仅代理第一层属性)
this.__v_isShallow = __v_isShallow;
this.dep = void 0;
//判断是否是RefImpl对象的标识,如果是的话,那么就直接返回value
this.__v_isRef = true;
///_rawValue存放的时最原始的值,就是去掉了响应式代理的值,toRaw会递归去掉代理
this._rawValue = __v_isShallow ? value : toRaw(value);
//如果toReactive()方法判断如果value是一个object 那么就用reactive() 包装 否则返回原值
//toReactive对于原始值 就直接返回value
//如果是对象的话 就是Proxy代理了
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
///收集依赖 ReactiveEffect 副作用对象
trackRefValue(this);
return this._value;
}
set value(newVal) {
// 判断是否直接使用原始值(不进行深度处理)
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
newVal = useDirectValue ? newVal : toRaw(newVal);
//判断新值和老值有没有变化,都是使用最原始的值来进行比较
if (hasChanged(newVal, this._rawValue)) {
const oldVal = this._rawValue;
this._rawValue = newVal;
this._value = useDirectValue ? newVal : toReactive(newVal);
triggerRefValue(this, 5, newVal, oldVal);
}
}
}
const toReactive = (value) => isObject(value) ? reactive(value) : value;
reactive(data)方式
如果data是原始类型的数据,就直接返回了原始值,这个方式是不能创建基于原始类型数据的响应式对象的,原始类型的数据 需要通过RefImpl 类来创建 。不会重复封装被reactive()封装过的值
function reactive(target) {
//判断是否只读
if (isReadonly(target)) {
return target;
}
return createReactiveObject(
target,
false,
mutableHandlers,//这个handler是下面的BaseReactiveHandler
mutableCollectionHandlers,
reactiveMap
);
}
//target这个方法并没有判断 当前target是否是RefImpl类型
function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {
if (!isObject(target)) {
{
warn$2(
`value cannot be made ${isReadonly2 ? "readonly" : "reactive"}: ${String(
target
)}`
);
}
return target;
}
///如果本身是响应式数据 就有__v_raw __v_isReactive两个属性
if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) {
return target;
}
//proxyMap全局缓存,响应式数据
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
///返回target 的类型 Object.prototype.toString.call()
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {
return target;
}
//如果是集合类型就用collectionHandlers处理,其他的用baseHandlers处理
const proxy = new Proxy(
target,
targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
);
proxyMap.set(target, proxy);
return proxy;
}
class BaseReactiveHandler {
constructor(_isReadonly = false, _isShallow = false) {
this._isReadonly = _isReadonly;
this._isShallow = _isShallow;
}
get(target, key, receiver) {
const isReadonly2 = this._isReadonly, isShallow2 = this._isShallow;
///通过get 方法设置了几个属性的陷阱 __v_isReactive __v_isReadonly __v_isShallow
//__v_raw 这个属性是返回最原始的值
if (key === "__v_isReactive") {
return !isReadonly2;
} else if (key === "__v_isReadonly") {
return isReadonly2;
} else if (key === "__v_isShallow") {
return isShallow2;
} else if (key === "__v_raw") {
if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || // receiver is not the reactive proxy, but has the same prototype
// this means the reciever is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) {
return target;
}
return;
}
const targetIsArray = isArray(target);
if (!isReadonly2) {
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
if (key === "hasOwnProperty") {
return hasOwnProperty;
}
}
const res = Reflect.get(target, key, receiver);
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res;
}
if (!isReadonly2) {
track(target, "get", key);
}
if (isShallow2) {
return res;
}
if (isRef(res)) {
return targetIsArray && isIntegerKey(key) ? res : res.value;
}
if (isObject(res)) {
return isReadonly2 ? readonly(res) : reactive(res);
}
return res;
}
}
使用reactive() 方法创建的对象有一个这个属性 __v_reactive
Proxy代理对象创建
class BaseReactiveHandler {
constructor(_isReadonly = false, _isShallow = false) {
this._isReadonly = _isReadonly;
this._isShallow = _isShallow;
}
get(target, key, receiver) {
const isReadonly2 = this._isReadonly, isShallow2 = this._isShallow;
///__v_isReactive 陷阱属性
if (key === "__v_isReactive") {
return !isReadonly2;
} else if (key === "__v_isReadonly") {
return isReadonly2;
} else if (key === "__v_isShallow") {
return isShallow2;
} else if (key === "__v_raw") {
///proxy对象通过get 设置了__v_raw 陷阱属性,通过这个属性可以判断是否是Proxy对象
if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || // receiver is not the reactive proxy, but has the same prototype
// this means the reciever is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) {
return target;
}
return;
}
//省略了部分代码
}
}
响应式工具API
isRef
作用
判断当前对象是不是响应式(RefImpl)对象,
function isRef(r) {
//使用RefImpl封装的对象 都有这个属性__v_isRef,且为true. 不管r是原始值还是对象类型
return !!(r && r.__v_isRef === true);
}
unref
作用
去掉响应式封装,不会递归
function unref(ref2) {
///isRef函数根据__v_isRef属性来判断是否是有RefImpl的封装。如果是的话 就返回value值。这个value只也可能还是reactive()响应式对象。
return isRef(ref2) ? ref2.value : ref2;
}
isReactive
作用判断value是否是一个reactive()创建的一个响应式对象
function isReactive(value) {
if (isReadonly(value)) {
return isReactive(value["__v_raw"]); //__v_raw属性是原始对象值
}
//__v_isReactive这个属性是在reactive()创建是通过get方法设置的一个陷阱属性。
return !!(value && value["__v_isReactive"]);
}
isProxy
作用
function isProxy(value) {
return value ? !!value["__v_raw"] : false;
}
isReadonly
作用
判断一个对象是否是只读的,有__v_isReadonly属性就是,只读具体什么概念??:
function isReadonly(value) {
return !!(value && value["__v_isReadonly"]);
}
toRaw
作用就是把一个reactive创建的响应式对象递归返回最原始的值,如果是RefImpl创建的对象响应就不能解封了。如果是RefImpl可以通过toRaw(reactiveObj._value)来获取最原始值。
所以toRaw只对reactive创建的响应式对象才有用。那么RefImpl怎么解封呢?
function toRaw(observed) {
//__v_raw这个属性是使用reactive创建时通过get 方法设置的一个陷阱属性,具体实现
//在: BaseReactiveHandler中
const raw = observed && observed["__v_raw"];
return raw ? toRaw(raw) : observed;
}
proxyRefs
function proxyRefs(objectWithRefs) {
//如果objectWithRefs本身是reactive封装响应的式对象,就直接返回,
//如果不是响应式 就用Proxy代理一次,但是这里创建的proxy对应的shallowUnwrapHandlers不会加入响应式功能
return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers);
}
//handler如下shallowUnwrapHandlers
const shallowUnwrapHandlers = {
get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
set: (target, key, value, receiver) => {
const oldValue = target[key];
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
} else {
return Reflect.set(target, key, value, receiver);
}
}
};
//以下是组件初始化的部分代码
//用shallowUnwrapHandlers 拦截器 把setup结果封装成了代理Proxy但是没有响应式功能
instance.setupState = proxyRefs(setupResult);
{
///把结果setupState暴露到代理对象中去
exposeSetupStateOnRenderContext(instance);
}
function exposeSetupStateOnRenderContext(instance) {
const { ctx, setupState } = instance;
Object.keys(toRaw(setupState)).forEach((key) => {
if (!setupState.__isScriptSetup) {
if (isReservedPrefix(key[0])) {
warn$1(
`setup() return property ${JSON.stringify(
key
)} should not start with "$" or "_" which are reserved prefixes for Vue internals.`
);
return;
}
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
get: () => setupState[key],
set: NOOP
});
}
});
}
组件渲染时处理data和setupState
setupState是setup函数返回的结果。
在vue3内部api中的applyOptions方法中会处理data值。这个方式applyOptions是为了兼容vue2的选项式api的
把instance.ctx上的属性代理到instance.proxy上。
PublicInstanceProxyHandlers 的代码如下。可以从代码上看到页面上渲染时,如{{msg}},获取msg值的优先级别是:
SETUP函数返回结果 >data>ctx>props>publicPropertiesMap(全局的)>appContext.config.globalProperties
const PublicInstanceProxyHandlers = {
get({ _: instance }, key) {
if (key === "__v_skip") {
return true;
}
const { ctx, setupState, data, props, accessCache, type, appContext } = instance;
if (key === "__isVue") {
return true;
}
let normalizedProps;
if (key[0] !== "$") {
//先从访问缓存中获取
const n = accessCache[key];
if (n !== void 0) {
switch (n) {
case 1 /* SETUP */:
return setupState[key];
case 2 /* DATA */:
return data[key];
case 4 /* CONTEXT */:
return ctx[key];
case 3 /* PROPS */:
return props[key];
}
} else if (hasSetupBinding(setupState, key)) {
accessCache[key] = 1 /* SETUP */;
return setupState[key];
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache[key] = 2 /* DATA */;
return data[key];
} else if (
// only cache other properties when instance has declared (thus stable)
// props
(normalizedProps = instance.propsOptions[0]) && hasOwn(normalizedProps, key)
) {
accessCache[key] = 3 /* PROPS */;
return props[key];
} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
accessCache[key] = 4 /* CONTEXT */;
return ctx[key];
} else if (shouldCacheAccess) {
accessCache[key] = 0 /* OTHER */;
}
}
///返回公共属性的getter
const publicGetter = publicPropertiesMap[key];
let cssModule, globalProperties;
if (publicGetter) {
if (key === "$attrs") {
track(instance.attrs, "get", "");
markAttrsAccessed();
} else if (key === "$slots") {
track(instance, "get", key);
}
return publicGetter(instance);
} else if (
// css module (injected by vue-loader)
(cssModule = type.__cssModules) && (cssModule = cssModule[key])
) {
return cssModule;
} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
accessCache[key] = 4 /* CONTEXT */;
return ctx[key];
} else if (
// global properties
globalProperties = appContext.config.globalProperties, hasOwn(globalProperties, key)
) {
{
return globalProperties[key];
}
} else if (currentRenderingInstance && (!isString(key) || // #1091 avoid internal isRef/isVNode checks on component instance leading
// to infinite warning loop
key.indexOf("__v") !== 0)) {
if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) {
warn$1(
`Property ${JSON.stringify(
key
)} must be accessed via $data because it starts with a reserved character ("$" or "_") and is not proxied on the render context.`
);
} else if (instance === currentRenderingInstance) {
warn$1(
`Property ${JSON.stringify(key)} was accessed during render but is not defined on instance.`
);
}
}
},
set({ _: instance }, key, value) {
const { data, setupState, ctx } = instance;
if (hasSetupBinding(setupState, key)) {
setupState[key] = value;
return true;
} else if (setupState.__isScriptSetup && hasOwn(setupState, key)) {
warn$1(`Cannot mutate <script setup> binding "${key}" from Options API.`);
return false;
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
data[key] = value;
return true;
} else if (hasOwn(instance.props, key)) {
warn$1(`Attempting to mutate prop "${key}". Props are readonly.`);
return false;
}
if (key[0] === "$" && key.slice(1) in instance) {
warn$1(
`Attempting to mutate public property "${key}". Properties starting with $ are reserved and readonly.`
);
return false;
} else {
if (key in instance.appContext.config.globalProperties) {
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
value
});
} else {
ctx[key] = value;
}
}
return true;
},
has({
_: { data, setupState, accessCache, ctx, appContext, propsOptions }
}, key) {
let normalizedProps;
return !!accessCache[key] || data !== EMPTY_OBJ && hasOwn(data, key) || hasSetupBinding(setupState, key) || (normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key) || hasOwn(ctx, key) || hasOwn(publicPropertiesMap, key) || hasOwn(appContext.config.globalProperties, key);
},
defineProperty(target, key, descriptor) {
if (descriptor.get != null) {
target._.accessCache[key] = 0;
} else if (hasOwn(descriptor, "value")) {
this.set(target, key, descriptor.value, null);
}
return Reflect.defineProperty(target, key, descriptor);
}
};
在vue3内部api中的handleSetupResult方法中会处理setup函数返回的结果setupResult值。
响应式对象处理实例
实例1
var refObj=ref({num1:123,num2:456});
var tmpObj=toRaw(refObj);
var reactiveObj=reactive(tmpObj);
结果1: tmpObj==refObj //true
reactiveObj==refObj //false
因为: refObj首先是一个RefImpl对象,然后被封装的值是一个对象类型的,所以{num1:123,num2:456}会被封装成一个reactive()对象。所以refObj.value是一个响应式对象(Proxy)。然后使用toRaw(refObj),进行解封,但是由于refObj是一个RefImpl类型的对象,不能使用toRaw()方法类解封,因为这个toRaw是通过__v_raw陷阱属性类判断的,refImpl类型的没有这陷阱属性__v_raw。所以oRaw(refObj)还是原样返回refObj。所以 tmpObj==refObj 。
var reactiveObj=reactive(tmpObj); 因为tmpObj是一个RefImpl类型的对象,而使用reactive创建响应式对象时是不会判断当前是RefImpl类型的,只有tmpObj是reactive类型的才不会再次封装。所以这时要回给tmpObj在包装一层。 reactiveObj==refObj 为false.
总结1:
toRaw 和unref都是分别解封 reactive和RefImpl类型,获取原始值。toRaw递归获取最原始的值。
ref(value)在封装value之前会判断当前是否是 RefImpl,如果是就直接返回,不会再封装。
reactive(value) 在封装之前也会判断value之前是否是reactive封装过,如果封装过就直接返回,不会再封装