Skip to content

feat(reactivity): improve support of getter usage in reactivity APIs #7997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 2, 2023
Next Next commit
feat(reactivity): support converting getter to ref with toRef()
  • Loading branch information
yyx990803 committed Mar 31, 2023
commit 88f663b77073bdac660933413912957833bf570e
7 changes: 7 additions & 0 deletions packages/dts-test/ref.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,13 @@ expectType<Ref<string>>(p2.obj.k)
// Should not distribute Refs over union
expectType<Ref<number | string>>(toRef(obj, 'c'))

expectType<Ref<number>>(toRef(() => 123))
expectType<Ref<number | string>>(toRef(() => obj.c))

const r = toRef(() => 123)
// @ts-expect-error
r.value = 234

// toRefs
expectType<{
a: Ref<number>
Expand Down
9 changes: 9 additions & 0 deletions packages/reactivity/__tests__/ref.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@ describe('reactivity/ref', () => {
expect(x.value).toBe(1)
})

test('toRef getter', () => {
const x = toRef(() => 1)
expect(x.value).toBe(1)
expect(isRef(x)).toBe(true)
expect(unref(x)).toBe(1)
//@ts-expect-error
expect(() => (x.value = 123)).toThrow()
})

test('toRefs', () => {
const a = reactive({
x: 1,
Expand Down
35 changes: 25 additions & 10 deletions packages/reactivity/src/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
triggerEffects
} from './effect'
import { TrackOpTypes, TriggerOpTypes } from './operations'
import { isArray, hasChanged, IfAny } from '@vue/shared'
import { isArray, hasChanged, IfAny, isFunction } from '@vue/shared'
import {
isProxy,
toRaw,
Expand Down Expand Up @@ -333,6 +333,14 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
}
}

class GetterRefImpl<T> {
public readonly __v_isRef = true
constructor(private readonly _getter: () => T) {}
get value() {
return this._getter()
}
}

export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>

/**
Expand Down Expand Up @@ -362,6 +370,9 @@ export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>
* @param key - Name of the property in the reactive object.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#toref}
*/
export function toRef<T extends () => any>(
getter: T
): T extends () => infer R ? Readonly<ShallowRef<R>> : never
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K
Expand All @@ -371,15 +382,19 @@ export function toRef<T extends object, K extends keyof T>(
key: K,
defaultValue: T[K]
): ToRef<Exclude<T[K], undefined>>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue?: T[K]
): ToRef<T[K]> {
const val = object[key]
return isRef(val)
? val
: (new ObjectRefImpl(object, key, defaultValue) as any)
export function toRef(
objectOrGetter: Record<string, any> | (() => unknown),
key?: string,
defaultValue?: unknown
): Ref {
if (isFunction(objectOrGetter)) {
return new GetterRefImpl(objectOrGetter as () => unknown) as any
} else {
const val = objectOrGetter[key!]
return isRef(val)
? val
: (new ObjectRefImpl(objectOrGetter, key!, defaultValue) as any)
}
}

// corner case when use narrows type
Expand Down