From 38f2dca310ee37cac0fdb8bd36f50c8fdc24fbb8 Mon Sep 17 00:00:00 2001 From: biubiukam Date: Wed, 17 Jun 2026 17:49:46 +0800 Subject: [PATCH] fix(type): restrict useWatch typed paths --- docs/examples/useWatch.tsx | 4 +--- src/hooks/useWatch.ts | 11 +++++++---- tests/useWatch.test.tsx | 26 ++++++++++++++++++++++---- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/examples/useWatch.tsx b/docs/examples/useWatch.tsx index 1475b7896..2225bbcae 100644 --- a/docs/examples/useWatch.tsx +++ b/docs/examples/useWatch.tsx @@ -47,10 +47,8 @@ export default () => { const demo2 = Form.useWatch(['demo1', 'demo2'], form); const demo3 = Form.useWatch(['demo1', 'demo2', 'demo3'], form); const demo4 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4'], form); - const demo5 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4', 'demo5'], form); - const more = Form.useWatch(['age', 'name', 'gender'], form); const hidden = Form.useWatch(['hidden'], { form, preserve: true }); - console.log('main watch', values, demo1, demo2, main, age, demo3, demo4, demo5, more, hidden); + console.log('main watch', values, demo1, demo2, main, age, demo3, demo4, hidden); return ( <>
= T extends Promise ? ValueType : never; type GetGeneric = ReturnPromise>; +type IsAny = 0 extends 1 & T ? true : false; +type AnyNamePath = + IsAny> extends true ? NamePath : never; export function stringify(value: any) { try { @@ -75,13 +78,13 @@ function useWatch( // ------- selector type end ------- function useWatch( - dependencies: NamePath, + dependencies: AnyNamePath, form?: TForm | WatchOptions, ): any; -function useWatch( - dependencies: NamePath, - form?: FormInstance | WatchOptions, +function useWatch( + dependencies: AnyNamePath, + form?: TForm | WatchOptions, ): ValueType; function useWatch( diff --git a/tests/useWatch.test.tsx b/tests/useWatch.test.tsx index b3d997ff7..0638537d2 100644 --- a/tests/useWatch.test.tsx +++ b/tests/useWatch.test.tsx @@ -8,6 +8,11 @@ import { Input } from './common/InfoField'; import { stringify } from '../src/hooks/useWatch'; import { changeValue } from './common'; +type IsAny = 0 extends 1 & T ? true : false; +type Equal = + (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false; +type Expect = T; + describe('useWatch', () => { beforeEach(() => { jest.useFakeTimers(); @@ -274,6 +279,8 @@ describe('useWatch', () => { demo?: string; demo2?: string; id?: number; + value3: string; + path1?: { path2?: number }; demo1?: { demo2?: { demo3?: { demo4?: string } } }; }; @@ -286,9 +293,19 @@ describe('useWatch', () => { const demo2 = Form.useWatch(['demo1', 'demo2'], form); const demo3 = Form.useWatch(['demo1', 'demo2', 'demo3'], form); const demo4 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4'], form); - const demo5 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4', 'demo5'], form); - const more = Form.useWatch(['age', 'name', 'gender'], form); + const value3 = Form.useWatch('value3', form); + const pathValue = Form.useWatch(['path1', 'path2'], form); const demo = Form.useWatch(['demo']); + const typeCheck: [ + Expect, false>>, + Expect>, + Expect>, + ] = [true, true, true]; + + // @ts-expect-error not a field of FieldType + Form.useWatch('unknown', form); + // @ts-expect-error not a nested field of FieldType + Form.useWatch(['path1', 'unknown'], form); const values2 = Form.useWatch( _values => ({ newName: _values.name, newAge: _values.age }), @@ -309,9 +326,10 @@ describe('useWatch', () => { demo2, demo3, demo4, - demo5, - more, + value3, + pathValue, demo, + typeCheck, values2, values3, })}