Skip to content

Commit

Permalink
chore: fix recur fieldPath type error when not passing generics type (#…
Browse files Browse the repository at this point in the history
…2259)

* fix: fix type error when formApi not pass generics, #2245
* chore: improve getValue type
* chore: add export FieldPathValue type
  • Loading branch information
pointhalo committed May 29, 2024
1 parent b02b23b commit db63f22
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 19 deletions.
38 changes: 27 additions & 11 deletions packages/semi-foundation/form/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
60 changes: 53 additions & 7 deletions packages/semi-ui/form/_story/form.stories.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -139,8 +138,6 @@ const Fields: FunctionComponent<FormFCChild> = ({ formState, values, formApi })

stories.add('Form', () => <Form>{Fields}</Form>);



interface IProps {
[x:string]: any;
}
Expand All @@ -157,7 +154,7 @@ interface FData {
},
test5: {
kkk: {
jjj: string
jjj: number
}
}
testK: boolean;
Expand All @@ -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() {
Expand All @@ -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>);
Expand Down
1 change: 0 additions & 1 deletion packages/semi-ui/form/hooks/useFieldApi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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),
});

Expand Down

0 comments on commit db63f22

Please sign in to comment.