Skip to content

Commit d5d3ca3

Browse files
crazyairzombieJ
andauthored
feat: Add name type check (#603)
* feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: 优化写法 * feat: test * feat: 支持 list * feat: 类型优化 * feat: 类型优化 * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: ts * feat: 备注 * feat: ts * chore: update desc * feat: 重新整理类型 * feat: 重新整理类型 * feat: 重新整理类型 * feat: 添加注释 * feat: 兼容推导对象 * feat: add test * feat: test * chore: update ts * feat: test * chore: update ts * feat: test * feat: 过滤方法 * feat: 支持 bool * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: testa * feat: testa * chore: comment it * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test --------- Co-authored-by: 二货机器人 <[email protected]>
1 parent e2ef992 commit d5d3ca3

File tree

7 files changed

+152
-11
lines changed

7 files changed

+152
-11
lines changed

docs/examples/basic.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import Form, { Field } from 'rc-field-form';
22
import React from 'react';
33
import Input from './components/Input';
44

5+
type FormData = {
6+
name?: string;
7+
password?: string;
8+
password2?: string;
9+
};
10+
511
export default () => {
612
const [form] = Form.useForm();
713

@@ -13,11 +19,11 @@ export default () => {
1319
console.error('fields:', fields);
1420
}}
1521
>
16-
<Field name="name">
22+
<Field<FormData> name="name">
1723
<Input placeholder="Username" />
1824
</Field>
1925

20-
<Field dependencies={['name']}>
26+
<Field<FormData> dependencies={['name']}>
2127
{() => {
2228
return form.getFieldValue('name') === '1' ? (
2329
<Field name="password">
@@ -32,7 +38,7 @@ export default () => {
3238
const password = form.getFieldValue('password');
3339
console.log('>>>', password);
3440
return password ? (
35-
<Field name="password2">
41+
<Field<FormData> name={['password2']}>
3642
<Input placeholder="Password 2" />
3743
</Field>
3844
) : null;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,6 @@
8080
"react-dom": "^16.14.0",
8181
"react-redux": "^4.4.10",
8282
"redux": "^3.7.2",
83-
"typescript": "^4.6.3"
83+
"typescript": "^5.1.6"
8484
}
8585
}

src/Field.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export interface InternalFieldProps<Values = any> {
9696

9797
export interface FieldProps<Values = any>
9898
extends Omit<InternalFieldProps<Values>, 'name' | 'fieldContext'> {
99-
name?: NamePath;
99+
name?: NamePath<Values>;
100100
}
101101

102102
export interface FieldState {

src/List.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ export interface ListOperations {
1919
move: (from: number, to: number) => void;
2020
}
2121

22-
export interface ListProps {
23-
name: NamePath;
22+
export interface ListProps<Values = any> {
23+
name: NamePath<Values>;
2424
rules?: ValidatorRule[];
2525
validateTrigger?: string | string[] | false;
2626
initialValue?: any[];
@@ -34,14 +34,14 @@ export interface ListProps {
3434
isListField?: boolean;
3535
}
3636

37-
const List: React.FunctionComponent<ListProps> = ({
37+
function List<Values = any>({
3838
name,
3939
initialValue,
4040
children,
4141
rules,
4242
validateTrigger,
4343
isListField,
44-
}) => {
44+
}: ListProps<Values>) {
4545
const context = React.useContext(FieldContext);
4646
const wrapperListContext = React.useContext(ListContext);
4747
const keyRef = React.useRef({
@@ -197,6 +197,6 @@ const List: React.FunctionComponent<ListProps> = ({
197197
</FieldContext.Provider>
198198
</ListContext.Provider>
199199
);
200-
};
200+
}
201201

202202
export default List;

src/interface.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { ReactElement } from 'react';
2+
import type { DeepNamePath } from './namePathType';
23
import type { ReducerAction } from './useForm';
34

45
export type InternalNamePath = (string | number)[];
5-
export type NamePath = string | number | InternalNamePath;
6+
export type NamePath<T = any> = DeepNamePath<T>;
67

78
export type StoreValue = any;
89
export type Store = Record<string, StoreValue>;

src/namePathType.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Store: The store type from `FormInstance<Store>`
3+
* ParentNamePath: Auto generate by nest logic. Do not fill manually.
4+
*/
5+
export type DeepNamePath<
6+
Store = any,
7+
ParentNamePath extends any[] = [],
8+
> = ParentNamePath['length'] extends 10
9+
? never
10+
: // Follow code is batch check if `Store` is base type
11+
true extends (Store extends string | number | boolean ? true : false)
12+
? ParentNamePath['length'] extends 0
13+
? Store // Return `string | number | boolean` instead of array if `ParentNamePath` is empty
14+
: never
15+
: true extends (Store extends (string | number | boolean)[] ? true : false)
16+
? ParentNamePath['length'] extends 0
17+
? Store // Return `(string | number | boolean)[]` instead of array if `ParentNamePath` is empty
18+
: [...ParentNamePath, number] // Connect path
19+
: Store extends any[] // Check if `Store` is `any[]`
20+
? // Connect path. e.g. { a: { b: string }[] }
21+
// Get: [a] | [ a,number] | [ a ,number , b]
22+
[...ParentNamePath, number] | DeepNamePath<Store[number], [...ParentNamePath, number]>
23+
: {
24+
// Convert `Store` to <key, value>. We mark key a `FieldKey`
25+
[FieldKey in keyof Store]: Store[FieldKey] extends Function
26+
? never
27+
:
28+
| (ParentNamePath['length'] extends 0 ? FieldKey : never) // If `ParentNamePath` is empty, it can use `FieldKey` without array path
29+
| [...ParentNamePath, FieldKey] // Exist `ParentNamePath`, connect it
30+
| DeepNamePath<Required<Store>[FieldKey], [...ParentNamePath, FieldKey]>; // If `Store[FieldKey]` is object
31+
}[keyof Store];

tests/nameTypeCheck.test.tsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
import React from 'react';
3+
import { render } from '@testing-library/react';
4+
import Form, { Field, List } from '../src';
5+
import type { NamePath } from '../src/interface';
6+
7+
describe('nameTypeCheck', () => {
8+
it('typescript', () => {
9+
type FieldType = {
10+
a: string;
11+
b?: string[];
12+
c?: { c1?: string; c2?: string[]; c3?: boolean[] }[];
13+
d?: { d1?: string[]; d2?: string };
14+
e?: { e1?: { e2?: string; e3?: string[]; e4: { e5: { e6: string } } } };
15+
list?: { age?: string }[];
16+
};
17+
18+
type fieldType = NamePath<FieldType>;
19+
20+
const Demo: React.FC = () => {
21+
return (
22+
<Form>
23+
{/* 无类型 */}
24+
<Field name={[]} />
25+
<Field name={'a'} />
26+
<Field name={['a']} />
27+
<Field name={12} />
28+
<Field name={[11]} />
29+
<Field name={['d', 1]} />
30+
<Field name={['d', 'd1']} />
31+
<Field name={[1, 2]} />
32+
<Field name={[true, false]} />
33+
{/* <Field name={{ aa: '111' }} /> */}
34+
{/* 有类型 */}
35+
<Field<FieldType> name={'a'} />
36+
<Field<FieldType> name={'b'} />
37+
<Field<FieldType> name={'c'} />
38+
<Field<FieldType> name={'d'} />
39+
<Field<FieldType> name={'e'} />
40+
<Field<FieldType> name={['a']} />
41+
<Field<FieldType> name={['b']} />
42+
<Field<FieldType> name={['c']} />
43+
<Field<FieldType> name={['d']} />
44+
<Field<FieldType> name={['e']} />
45+
<Field<FieldType> name={['b', 1]} />
46+
<Field<FieldType> name={['c', 1]} />
47+
<Field<FieldType> name={['c', 1, 'c1']} />
48+
<Field<FieldType> name={['c', 1, 'c2']} />
49+
<Field<FieldType> name={['c', 1, 'c2', 1]} />
50+
<Field<FieldType> name={['c', 1, 'c3']} />
51+
<Field<FieldType> name={['c', 1, 'c3', 1]} />
52+
<Field<FieldType> name={['d', 'd1']} />
53+
<Field<FieldType> name={['d', 'd1', 1]} />
54+
<Field<FieldType> name={['d', 'd2']} />
55+
<Field<FieldType> name={['e', 'e1']} />
56+
<Field<FieldType> name={['e', 'e1', 'e2']} />
57+
<Field<FieldType> name={['e', 'e1', 'e3']} />
58+
<Field<FieldType> name={['e', 'e1', 'e3', 1]} />
59+
<Field<FieldType> name={['e', 'e1', 'e4']} />
60+
<Field<FieldType> name={['e', 'e1', 'e4', 'e5']} />
61+
<Field<FieldType> name={['e', 'e1', 'e4', 'e5', 'e6']} />
62+
{/* list */}
63+
<List<FieldType> name={'list'}>
64+
{fields => {
65+
return fields.map(field => (
66+
<Field<FieldType['list']> {...field} name={[1, 'age']} key={field.key} />
67+
));
68+
}}
69+
</List>
70+
</Form>
71+
);
72+
};
73+
render(<Demo />);
74+
});
75+
it('type inference', () => {
76+
interface Props<T = any> {
77+
data?: T[];
78+
list?: { name?: NamePath<T> }[];
79+
}
80+
function func<T = any>(props: Props<T>) {
81+
return props;
82+
}
83+
func({ data: [{ a: { b: 'c' } }], list: [{ name: ['a', 'b'] }] });
84+
});
85+
it('more type', () => {
86+
// Moment
87+
type t1 = NamePath<{ a: { b: string; func: Moment } }>;
88+
// Function
89+
type t2 = NamePath<{ a: { b: string; func: () => { c: string } } }>;
90+
91+
interface Moment {
92+
func2: Function;
93+
format: (format?: string) => string;
94+
}
95+
});
96+
it('tree', () => {
97+
type t1 = NamePath<{ a: TreeNode }>;
98+
99+
interface TreeNode {
100+
child: TreeNode[];
101+
}
102+
});
103+
});

0 commit comments

Comments
 (0)