【vue3】响应式的几个重要API
- 互联网
- 2025-09-07 07:24:01

创建响应式对象 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和setupStatesetupState是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值。
响应式对象处理实例 实例1var 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封装过,如果封装过就直接返回,不会再封装
【vue3】响应式的几个重要API由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【vue3】响应式的几个重要API”