Просмотр исходного кода

fix(types): allow customRef to have different getter/setter types (#14639)

The customRef function now supports two type parameters <T, S> like Ref<T, S>,
allowing the getter to return type T while the setter accepts type S.

Previously, CustomRefFactory only accepted a single type parameter T, which
was used for both getter and setter. This prevented use cases where the setter
should accept a different type than what the getter returns (e.g., parsing
or transformation scenarios).
Rayan Salhab 2 месяцев назад
Родитель
Сommit
e20ddb0018
2 измененных файлов с 31 добавлено и 9 удалено
  1. 20 0
      packages-private/dts-test/ref.test-d.ts
  2. 11 9
      packages/reactivity/src/ref.ts

+ 20 - 0
packages-private/dts-test/ref.test-d.ts

@@ -8,6 +8,7 @@ import {
   type ToRefs,
   type WritableComputedRef,
   computed,
+  customRef,
   isRef,
   proxyRefs,
   reactive,
@@ -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'
+})

+ 11 - 9
packages/reactivity/src/ref.ts

@@ -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)
   }
 }
@@ -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
 }