]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(types): use private branding for shallowReactive (#14641)
authoredison <daiwei521@126.com>
Fri, 3 Apr 2026 02:31:00 +0000 (10:31 +0800)
committerGitHub <noreply@github.com>
Fri, 3 Apr 2026 02:31:00 +0000 (10:31 +0800)
fix #14638
fix shallowReactive type regressions introduced by #14493

packages-private/dts-test/reactivity.test-d.ts
packages-private/dts-test/ref.test-d.ts
packages/reactivity/src/reactive.ts
packages/reactivity/src/ref.ts

index 58d36d10362ca4ae53d97dd6cdc742f38446d294..270b83da8f4f0ac54008084d563434214166c39c 100644 (file)
@@ -140,3 +140,9 @@ describe('shallowReactive marker should not leak into value unions', () => {
   const value = {} as (typeof state)[keyof typeof state]
   expectType<string>(value.title)
 })
+
+describe('shallowReactive type should accept plain object assignment', () => {
+  const shallow = shallowReactive({ a: 1, b: 2 })
+  let values: typeof shallow
+  values = { a: 1, b: 2 }
+})
index 9b6f699bf47ffed94721cf6d3fcc67152f939fab..b6568414d4889abbab68c3b8d0432f112c054075 100644 (file)
@@ -321,6 +321,12 @@ expectType<undefined>(p2.u)
 expectType<Ref<string>>(p2.obj.k)
 expectType<{ name: string } | null>(p2.union)
 
+const r3 = shallowReactive({
+  n: ref(1),
+})
+const p3 = proxyRefs(r3)
+expectType<Ref<number>>(p3.n)
+
 // toRef and toRefs
 {
   const obj: {
@@ -437,6 +443,15 @@ describe('shallow reactive in reactive', () => {
   expectType<number>(foo.value.a.b.value)
 })
 
+describe('shallow reactive collection in reactive', () => {
+  const baz = reactive({
+    foo: shallowReactive(new Map([['a', ref(42)]])),
+  })
+
+  const foo = toRef(baz, 'foo')
+  expectType<Ref<number> | undefined>(foo.value.get('a'))
+})
+
 describe('shallow ref in reactive', () => {
   const x = reactive({
     foo: shallowRef({
index 16d0394cbc435d9829f0315af7a234fcea1515c5..c9a412eb89301c5622c4c04bc9370119e5fe3de2 100644 (file)
@@ -104,9 +104,16 @@ export function reactive(target: object) {
   )
 }
 
-export declare const ShallowReactiveMarker: unique symbol
+// Use a private class brand instead of a marker property so shallow-reactive
+// types remain distinguishable in `UnwrapRef` without leaking the brand into
+// `keyof`/indexed access types or requiring the property for plain assignment.
+declare class ShallowReactiveBrandClass {
+  private __shallowReactiveBrand?: never
+}
+
+export type ShallowReactiveBrand = ShallowReactiveBrandClass
 
-export type ShallowReactive<T> = T & { [ShallowReactiveMarker]: never }
+export type ShallowReactive<T> = T & ShallowReactiveBrand
 
 /**
  * Shallow version of {@link reactive}.
index f9c6d03e0eab2888c3a07de283fe62e6cac687d1..1459c1da22d9fa46147d275b2a3de20132bb06ab 100644 (file)
@@ -10,7 +10,7 @@ import {
 import { Dep, getDepFromReactive } from './dep'
 import {
   type Builtin,
-  type ShallowReactiveMarker,
+  type ShallowReactiveBrand,
   type Target,
   isProxy,
   isReactive,
@@ -539,9 +539,11 @@ function propertyToRef(
  */
 export interface RefUnwrapBailTypes {}
 
-export type ShallowUnwrapRef<T> = {
-  [K in keyof T]: DistributeRef<T[K]>
-}
+export type ShallowUnwrapRef<T> = T extends ShallowReactiveBrand
+  ? T
+  : {
+      [K in keyof T]: DistributeRef<T[K]>
+    }
 
 type DistributeRef<T> = T extends Ref<infer V, unknown> ? V : T
 
@@ -558,19 +560,20 @@ export type UnwrapRefSimple<T> = T extends
   | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
   | { [RawSymbol]?: true }
   ? T
-  : T extends Map<infer K, infer V>
-    ? Map<K, UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Map<any, any>>>
-    : T extends WeakMap<infer K, infer V>
-      ? WeakMap<K, UnwrapRefSimple<V>> &
-          UnwrapRef<Omit<T, keyof WeakMap<any, any>>>
-      : T extends Set<infer V>
-        ? Set<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Set<any>>>
-        : T extends WeakSet<infer V>
-          ? WeakSet<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof WeakSet<any>>>
-          : T extends ReadonlyArray<any>
-            ? { [K in keyof T]: UnwrapRefSimple<T[K]> }
-            : T extends object & { [ShallowReactiveMarker]: never }
-              ? T
+  : T extends ShallowReactiveBrand
+    ? T
+    : T extends Map<infer K, infer V>
+      ? Map<K, UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Map<any, any>>>
+      : T extends WeakMap<infer K, infer V>
+        ? WeakMap<K, UnwrapRefSimple<V>> &
+            UnwrapRef<Omit<T, keyof WeakMap<any, any>>>
+        : T extends Set<infer V>
+          ? Set<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Set<any>>>
+          : T extends WeakSet<infer V>
+            ? WeakSet<UnwrapRefSimple<V>> &
+                UnwrapRef<Omit<T, keyof WeakSet<any>>>
+            : T extends ReadonlyArray<any>
+              ? { [K in keyof T]: UnwrapRefSimple<T[K]> }
               : T extends object
                 ? {
                     [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>