Skip to content
Open
21 changes: 21 additions & 0 deletions packages-private/dts-test/reactivity.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
readonly,
ref,
shallowReadonly,
toRaw,
} from 'vue'
import { describe, expectType } from './utils'

Expand Down Expand Up @@ -130,3 +131,23 @@ describe('should not error when assignment', () => {
record2 = arr
expectType<string>(record2[0])
})

// #7478
describe('readonly raw type', () => {
type Foo = { a: number; b: string; c: { d: number } }
const foo: Foo = {
a: 1,
b: 'b',
c: { d: 2 },
}

// readonly
const r = readonly(foo)
const rawObj = toRaw(r)
expectType<Foo>(rawObj)

// shallowReadonly
const shallowR = shallowReadonly(foo)
const shallowRawObj = toRaw(shallowR)
expectType<Foo>(shallowRawObj)
})
19 changes: 17 additions & 2 deletions packages/reactivity/src/reactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ import type { RawSymbol, Ref, UnwrapRefSimple } from './ref'
import { ReactiveFlags } from './constants'
import { warn } from './warning'

declare const ReadonlyRawSymbol: unique symbol

interface ReadonlyRaw<T> {
/**
* Original type used to hold readonly function parameters
*/
[ReadonlyRawSymbol]: T
}

export interface Target {
[ReactiveFlags.SKIP]?: boolean
[ReactiveFlags.IS_REACTIVE]?: boolean
Expand Down Expand Up @@ -204,7 +213,7 @@ export type DeepReadonly<T> = T extends Builtin
*/
export function readonly<T extends object>(
target: T,
): DeepReadonly<UnwrapNestedRefs<T>> {
): DeepReadonly<UnwrapNestedRefs<T>> & ReadonlyRaw<T> {
return createReactiveObject(
target,
true,
Expand Down Expand Up @@ -244,7 +253,9 @@ export function readonly<T extends object>(
* @param target - The source object.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowreadonly}
*/
export function shallowReadonly<T extends object>(target: T): Readonly<T> {
export function shallowReadonly<T extends object>(
target: T,
): Readonly<T> & ReadonlyRaw<T> {
return createReactiveObject(
target,
true,
Expand Down Expand Up @@ -375,6 +386,10 @@ export function isProxy(value: any): boolean {
* @param observed - The object for which the "raw" value is requested.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#toraw}
*/
export function toRaw<T extends ReadonlyRaw<any>>(
observed: T,
): T[typeof ReadonlyRawSymbol]
export function toRaw<T>(observed: T): T
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
Expand Down