在React中封装一个组件

背景

最近在学习React,看了许多教学视频,今天学到了一个封装组件较完善的方法,特此记录下来。

我们知道select标签经常有显示的问题,例如idname对应不上,原因在于value属性的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Select
value={param.personId}
onChange={(value) =>
setParam({
...param,
personId: value,
})
}
>
<Select.Option value={""}>负责人</Select.Option>
{users.map((user) => (
<Select.Option key={user.id} value={String(user.id)}>
{user.name}
</Select.Option>
))}
</Select>

Option中的value值在传给onChange回调函数中时,如果是number类型,但personId定义的又是string类型的话,就不会按预期显示,而是直接用传入的值。

于是现在封装一个组件id-select,解决上述问题。

封装组件

components文件夹下新建一个id-select.tsx文件,首先写好组件基本内容。

1
2
3
4
5
6
7
8
9
10
11
12
import { Select } from "antd";
import React from "react";



export const IdSelect = () => {
return (
<Select value={} onChange={}>
<Select.Option value={}>{}</Select.Option>
</Select>
)
}

我们要封装一个Select组件,使得其

  • value可以传入多种类型的值
  • onChange只会回调number | undefined类型
  • 当 isNaN(Number(value))为true 代表选择默认类型
  • 当选择默认类型,onChange回调undefined

定义下组件所需的props类型:

1
2
3
4
5
6
interface IdSelectProps {
value: string | number | null | undefined;
onChange: (value?: number) => void;
defaultOptionName?: string;
options?: { name: string; id: number }[];
}

定义一个辅助函数:

1
2
3
const toNumber = (value: unknown) => {
return isNaN(Number(value)) ? 0 : Number(value);
};

完善组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export const IdSelect = (props: IdSelectProps) => {
const { value, onChange, defaultOptionName, options } = props;
return (
<Select
value={toNumber(value)}
onChange={(value) => onChange(toNumber(value) || undefined)}
>
{defaultOptionName ? (
<Select.Option value={0}>{defaultOptionName}</Select.Option>
) : null}
{options?.map((option) => (
<Select.Option value={option.id} key={option.id}>
{option.name}
</Select.Option>
))}
</Select>
);
};

还有一个问题,如果我们需要用到Select本身的props呢,或者说我想改变Select本身的样式呢?

这就是一个props透传问题

React中有专门的处理方法,将接口改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 继承Select组件本身的props
type SelectProps = React.ComponentProps<typeof Select>;

// 去掉我们接口中定义的,防止重名
interface IdSelectProps
extends Omit<
SelectProps,
"value" | "onChange" | "defaultOptionName" | "options"
> {
value: string | number | null | undefined;
onChange: (value?: number) => void;
defaultOptionName?: string;
options?: { name: string; id: number }[];
}

然后传给里面的Select组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export const IdSelect = (props: IdSelectProps) => {
// 用扩展操作符 ...rest取得props中剩余的属性
const { value, onChange, defaultOptionName, options, ...rest } = props;
return (
<Select
value={toNumber(value)}
onChange={(value) => onChange(toNumber(value) || undefined)}
// 传给Select
{...rest}
>
{defaultOptionName ? (
<Select.Option value={0}>{defaultOptionName}</Select.Option>
) : null}
{options?.map((option) => (
<Select.Option value={option.id} key={option.id}>
{option.name}
</Select.Option>
))}
</Select>
);
};

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { Select } from "antd";
import React from "react";

type SelectProps = React.ComponentProps<typeof Select>;

interface IdSelectProps
extends Omit<
SelectProps,
"value" | "onChange" | "defaultOptionName" | "options"
> {
value: string | number | null | undefined;
onChange: (value?: number) => void;
defaultOptionName?: string;
options?: { name: string; id: number }[];
}

/**
*
* value 可以传入都多种类型的值
* onChange只会回调 number | undefined 类型
* 当 isNaN(Number(value))为true 代表选择默认类型
* 当选择默认类型,onChange回调undefined
*/
export const IdSelect = (props: IdSelectProps) => {
const { value, onChange, defaultOptionName, options, ...rest } = props;
return (
<Select
value={toNumber(value)}
onChange={(value) => onChange(toNumber(value) || undefined)}
{...rest}
>
{defaultOptionName ? (
<Select.Option value={0}>{defaultOptionName}</Select.Option>
) : null}
{options?.map((option) => (
<Select.Option value={option.id} key={option.id}>
{option.name}
</Select.Option>
))}
</Select>
);
};

const toNumber = (value: unknown) => {
return isNaN(Number(value)) ? 0 : Number(value);
};

一个简单的组件就封装好了

example

1
2
3
4
5
6
7
8
9
import React from "react";
import { useUser } from "screens/project-list/user";
import { IdSelect } from "./id-select";

export const UserSelect = (props: React.ComponentProps<typeof IdSelect>) => {
const { users } = useUser();
return <IdSelect options={users || []} {...props}></IdSelect>;
};