Jelajahi Sumber

chore: fix recur fieldPath type error when not passing generics type (#2259)

* fix: fix type error when formApi not pass generics, #2245
* chore: improve getValue type
* chore: add export FieldPathValue type
pointhalo 1 tahun lalu
induk
melakukan
db63f2244a

+ 27 - 11
packages/semi-foundation/form/interface.ts

@@ -36,22 +36,38 @@ export interface setValuesConfig {
     isOverride: boolean
 }
 
-type ExcludeStringNumberKeys<T> = {
-    [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]
-}
-
-type CustomKeys<T> = { [K in keyof T]: string extends K ? never : number extends K ? never : K; } extends { [K in keyof T]: infer U } ? U : never;
-
-type FieldPath<T, K extends CustomKeys<T> = CustomKeys<T>> = K extends string ? T[K] extends Record<string, any> ? `${K}.${FieldPath<T[K], CustomKeys<T[K]>>}` | K : K : never;
+// FieldPath 类型定义,用于生成对象字段的路径字符串
+export type FieldPath<T> = T extends object ? {
+    // 遍历对象的每个键 K
+    [K in keyof T]: T[K] extends object
+        // 如果键 K 对应的值是对象,则生成嵌套路径(递归调用 FieldPath)
+        ? `${string & K}.${FieldPath<T[K]>}` | `${string & K}`
+        // 否则,仅生成当前键的路径
+        : `${string & K}`;
+}[keyof T]
+    : never;
+
+// FieldPathValue 类型定义,用于从路径字符串中推导出实际的类型
+export type FieldPathValue<T, P extends FieldPath<T>> =
+  // 如果路径字符串 P 包含嵌套路径(使用模板字符串类型进行匹配)
+  P extends `${infer K}.${infer Rest}`
+      ? K extends keyof T
+          // 递归解析嵌套路径,逐层深入对象结构
+          ? Rest extends FieldPath<T[K]>
+              ? FieldPathValue<T[K], Rest>
+              : never
+          : never
+      // 如果路径字符串 P 是顶层键
+      : P extends keyof T
+          ? T[P]
+          : never;
 
 // use object replace Record<string, any>, fix issue 933
 export interface BaseFormApi<T extends object = any> {
-// export interface BaseFormApi<T extends object = any> {
     /** get value of field */
-    getValue: <K extends keyof T>(field?: K) => T[K];
+    getValue: <P extends FieldPath<T>>(field?: P) => FieldPathValue<T, P>;
     /** set value of field */
-    setValue: <K extends CustomKeys<T>>(field: FieldPath<T, K> | FieldPath<K>, newFieldValue: any) => void;
-    // setValue: <K extends keyof T>(field: K, newFieldValue: T[K]) => void;
+    setValue: <K extends FieldPath<T>>(field: K, newFieldValue: any) => void;
     /** get error of field */
     getError: <K extends keyof T>(field: K) => any;
     /** set error of field */

+ 53 - 7
packages/semi-ui/form/_story/form.stories.tsx

@@ -1,7 +1,6 @@
 import React, { FunctionComponent } from 'react';
 import { storiesOf } from '@storybook/react';
 import { Form, useFormState, useFormApi, withField, Input, Button, Upload, withFormApi, withFormState } from '../../index';
-import { values } from 'lodash';
 const stories = storiesOf('Form', module);
 import { FormApiContext } from '../context';
 
@@ -139,8 +138,6 @@ const Fields: FunctionComponent<FormFCChild> = ({ formState, values, formApi })
 
 stories.add('Form', () => <Form>{Fields}</Form>);
 
-
-
 interface IProps {
     [x:string]: any;
 }
@@ -157,7 +154,7 @@ interface FData {
     },
     test5: {
         kkk: {
-            jjj: string
+            jjj: number
         }
     }
     testK: boolean;
@@ -178,13 +175,25 @@ class Demo extends React.Component<IProps, IState> {
 
     setData() {
         const formApi = this.formApi;
+        // set
         formApi.setValue('test3', 123);
-        formApi.setValue('test8', 123);
         formApi.setValue('test4.event', 123);
         formApi.setValue('test5.kkk', 123);
         formApi.setValue('test5.kkk.jjj', 123);
-        formApi.setValue('test5.kkk.ppp', 123);
-        formApi.setValue('test4.5', 123);
+        formApi.setValue('keyNotExist', 123);
+        formApi.setValue('test4.notExist', 123);
+        formApi.setValue('test5.kkk.notExist', 123);
+
+        // get
+        let test3 = formApi.getValue('test3');
+        let test4 = formApi.getValue('test4');
+        let test4event = formApi.getValue('test4.event');
+        let test5kkk = formApi.getValue('test5.kkk');
+        let test5kkkjjj = formApi.getValue('test5.kkk.jjj');
+
+        let a = formApi.getValue('keyNotExist');
+        let b = formApi.getValue('test5.kkk.notExist');
+        let c = formApi.getValue('test4.notExist');
     }
 
     render() {
@@ -203,6 +212,43 @@ class Demo extends React.Component<IProps, IState> {
     }
   }
 
+class WithoutGenericsType extends React.Component<IProps, IState> {
+
+    formApi: FormApi
+
+    constructor(props: any) {
+        super(props);
+    }
+
+    getFormApi(formApi) {
+        this.formApi = formApi;
+    }
+
+    setData() {
+        const formApi = this.formApi;
+        formApi.setValue('test3', 123);
+        formApi.setValue('test8', 123);
+        formApi.setValue('test4.event', 123);
+        formApi.setValue('test5.kkk', 123);
+        formApi.setValue('test5.kkk.jjj', 123);
+        formApi.setValue('test5.kkk.ppp', 123);
+        formApi.setValue('test4.5', 123);
+    }
+
+    render() {
+        return (
+            <>
+                <Form
+                    getFormApi={this.getFormApi}
+                    onSubmit={values => console.log(values.test2)}
+                    onChange={formState => formState.values.test}
+                    validateFields={values => ({ test4: 'test4 empty', test2: '' })}
+                >
+                </Form>
+            </>
+        );
+    }
+}
 
 
 stories.add('Form render', () => <Form render={({values, formApi, formState}) => <div></div>}></Form>);

+ 0 - 1
packages/semi-ui/form/hooks/useFieldApi.tsx

@@ -9,7 +9,6 @@ const buildFieldApi = (formApi: FormApi, field: string) => ({
     getTouched: () => formApi.getTouched(field),
     setTouched: (isTouched: boolean) => formApi.setTouched(field, isTouched),
     getValue: () => formApi.getValue(field),
-    // @ts-ignore
     setValue: (value: any) => formApi.setValue(field, value),
 });