// Should not distribute Refs over union
expectType<Ref<number | string>>(toRef(obj, 'c'))
+ const array = reactive(['a', 'b'])
+ expectType<Ref<string>>(toRef(array, '1'))
+ expectType<Ref<string>>(toRef(array, '1', 'fallback'))
+
+ const tuple: [string, number] = ['a', 1]
+ expectType<Ref<string>>(toRef(tuple, '0'))
+ expectType<Ref<number>>(toRef(tuple, '1'))
+
expectType<Ref<number>>(toRef(() => 123))
expectType<Ref<number | string>>(toRef(() => obj.c))
expect(bar.value).toBe(6)
})
+ test('triggerRef on toRef created from array coerces property keys', () => {
+ const assertTriggerRef = (key: unknown) => {
+ const array = reactive(['a'])
+ const first = toRef(array as any, key as any)
+ const fn = vi.fn()
+
+ effect(() => fn(first.value))
+ expect(fn).toHaveBeenCalledTimes(1)
+
+ triggerRef(first)
+ expect(fn).toHaveBeenCalledTimes(2)
+ }
+
+ assertTriggerRef(0)
+ // JS coerces non-symbol property keys like [0] to the string "0".
+ assertTriggerRef([0])
+ })
+
+ test('triggerRef on toRef created from symbol key preserves the symbol', () => {
+ const key = Symbol()
+ const object = reactive({ [key]: 'a' })
+ const value = toRef(object, key)
+ const fn = vi.fn()
+
+ effect(() => fn(value.value))
+ expect(fn).toHaveBeenCalledTimes(1)
+
+ triggerRef(value)
+ expect(fn).toHaveBeenCalledTimes(2)
+ })
+
test('toRef default value', () => {
const a: { x: number | undefined } = { x: undefined }
const x = toRef(a, 'x', 1)
isFunction,
isIntegerKey,
isObject,
+ isSymbol,
} from '@vue/shared'
import { Dep, getDepFromReactive } from './dep'
import {
[K in keyof T]: ToRef<T[K]>
}
+type ArrayStringKey<T> = T extends readonly any[]
+ ? number extends T['length']
+ ? `${number}`
+ : never
+ : never
+
+type ToRefKey<T> = keyof T | ArrayStringKey<T>
+
+type ToRefValue<T extends object, K extends ToRefKey<T>> = K extends keyof T
+ ? T[K]
+ : T extends readonly (infer V)[]
+ ? K extends ArrayStringKey<T>
+ ? V
+ : never
+ : never
+
/**
* Converts a reactive object to a plain object where each property of the
* resulting object is a ref pointing to the corresponding property of the
public _value: T[K] = undefined!
private readonly _raw: T
+ private readonly _key: K
private readonly _shallow: boolean
constructor(
private readonly _object: T,
- private readonly _key: K,
+ key: K,
private readonly _defaultValue?: T[K],
) {
+ this._key = (isSymbol(key) ? key : String(key)) as K
this._raw = toRaw(_object)
let shallow = true
let obj = _object
// For an array with integer key, refs are not unwrapped
- if (!isArray(_object) || !isIntegerKey(String(_key))) {
+ if (!isArray(_object) || isSymbol(this._key) || !isIntegerKey(this._key)) {
// Otherwise, check each proxy layer for unwrapping
do {
shallow = !isProxy(obj) || isShallow(obj)
: T extends Ref
? T
: Ref<UnwrapRef<T>>
-export function toRef<T extends object, K extends keyof T>(
+export function toRef<T extends object, K extends ToRefKey<T>>(
object: T,
key: K,
-): ToRef<T[K]>
-export function toRef<T extends object, K extends keyof T>(
+): ToRef<ToRefValue<T, K>>
+export function toRef<T extends object, K extends ToRefKey<T>>(
object: T,
key: K,
- defaultValue: T[K],
-): ToRef<Exclude<T[K], undefined>>
+ defaultValue: ToRefValue<T, K>,
+): ToRef<Exclude<ToRefValue<T, K>, undefined>>
/*@__NO_SIDE_EFFECTS__*/
export function toRef(
- source: Record<string, any> | MaybeRef,
- key?: string,
+ source: Record<PropertyKey, any> | MaybeRef,
+ key?: string | number | symbol,
defaultValue?: unknown,
): Ref {
if (isRef(source)) {
}
function propertyToRef(
- source: Record<string, any>,
- key: string,
+ source: Record<PropertyKey, any>,
+ key: string | number | symbol,
defaultValue?: unknown,
) {
return new ObjectRefImpl(source, key, defaultValue) as any