Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages-private/dts-test/ref.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type ToRefs,
type WritableComputedRef,
computed,
customRef,
isRef,
proxyRefs,
reactive,
Expand Down Expand Up @@ -548,3 +549,22 @@ expectType<TemplateRef>(tRef)

const tRef2 = useTemplateRef<HTMLElement>('bar')
expectType<TemplateRef<HTMLElement>>(tRef2)

// #14637 customRef with different getter/setter types
describe('customRef with different getter/setter types', () => {
// customRef should support different getter/setter types like Ref<T, S>
const cr = customRef<string, number>((track, trigger) => ({
get: () => 'hello',
set: (val: number) => {
// setter accepts number, getter returns string
trigger()
},
}))

// getter returns string
expectType<string>(cr.value)
// setter accepts number
cr.value = 123
// @ts-expect-error setter doesn't accept string
cr.value = 'world'
})
20 changes: 11 additions & 9 deletions packages/reactivity/src/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,36 +285,36 @@ export function proxyRefs<T extends object>(
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

export type CustomRefFactory<T> = (
export type CustomRefFactory<T, S = T> = (
track: () => void,
trigger: () => void,
) => {
get: () => T
set: (value: T) => void
set: (value: S) => void
}

class CustomRefImpl<T> {
class CustomRefImpl<T, S = T> {
public dep: Dep

private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
private readonly _get: ReturnType<CustomRefFactory<T, S>>['get']
private readonly _set: ReturnType<CustomRefFactory<T, S>>['set']

public readonly [ReactiveFlags.IS_REF] = true

public _value: T = undefined!

constructor(factory: CustomRefFactory<T>) {
constructor(factory: CustomRefFactory<T, S>) {
const dep = (this.dep = new Dep())
const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep))
this._get = get
this._set = set
}

get value() {
get value(): T {
return (this._value = this._get())
}

set value(newVal) {
set value(newVal: S) {
this._set(newVal)
}
}
Expand All @@ -326,7 +326,9 @@ class CustomRefImpl<T> {
* @param factory - The function that receives the `track` and `trigger` callbacks.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#customref}
*/
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
export function customRef<T, S = T>(
factory: CustomRefFactory<T, S>,
): Ref<T, S> {
return new CustomRefImpl(factory) as any
}

Expand Down
Loading