React 组件中的条件 TypeScript 泛型
Source: Dev.to
在我之前的文章这里中,我承诺会继续讨论 TypeScript 泛型——这是我在使用 React 组件时认为尤为重要的话题。
在那篇文章里,我们介绍了泛型组件的基本定义,并看了一个关于何时以及为何使用泛型的简单示例。在本篇文章中,我们将迈出下一步,重点关注条件泛型以及它们在真实 React 组件中的应用。
这里,我想展示一个我在实际项目中使用的例子,它让我深刻体会到泛型类型的优雅与强大。
Source: …
问题:没有泛型的选择组件
让我们考虑一个选择组件,它可以根据属性选择一个或多个选项。
// Just an interface of option structure
interface Option {
id: number;
name: string;
}
// It's your value type
type Value = Option[] | Option | undefined;
// Component props interface
interface Props {
value?: Value;
options?: Option[];
multiple?: boolean;
onChange?: (value: Value) => void;
}
在使用该组件时,你 并不完全安全,因为 TypeScript 并不知道组件是处于多选模式还是单选模式。
value?: Value 和 onChange?: (value: Value) => void 在两种状态下始终可用,且不受 multiple?: boolean 的影响。这并不够严格——我们希望得到以下保证:
- 如果
multiple为false(或未提供) →value必须是 单个对象(或undefined); - 如果
multiple为true→value必须是 数组。
向 Props 接口引入泛型
我们保持相同的 Option 接口:
interface Option {
id: number;
name: string;
}
现在我们为 Props 增加两个泛型参数 O 和 M:
interface Props<O extends Option = Option, M extends boolean | undefined = undefined> {
value?: M extends undefined | false ? O | undefined : O[];
options?: O[];
multiple?: M;
onChange?: (
values: M extends undefined | false ? O | undefined : O[]
) => void;
}
说明
| 泛型 | 含义 |
|---|---|
O | 扩展 Option;表示选项类型。 |
M | 扩展 `boolean |
value | M extends undefined | false ? O | undefined : O[] – 当 multiple 为 false 或未提供时为单个 O(或 undefined),否则为数组 O[]。 |
onChange | 回调参数使用相同的条件类型。 |
提取条件类型
重复的条件表达式会显得冗余,因此我们将其提取为独立的泛型类型:
type Value<O extends Option, M extends boolean | undefined> =
M extends undefined | false ? O | undefined : O[];
现在 Props 变得更加简洁:
interface Props<O extends Option = Option, M extends boolean | undefined = undefined> {
value?: Value<O, M>;
options?: O[];
multiple?: M;
onChange?: (values: Value<O, M>) => void;
}
Source: …
为组件本身添加泛型
组件本身也必须是泛型的,并将这些参数传递给 Props:
export const SelectionExample = <
O extends Option = Option,
M extends boolean | undefined = undefined
>({
value = undefined,
multiple = false,
onChange = () => undefined,
options = [],
}: Props<O, M>) => {
const isOptionSelected = (option: O): boolean => {
// Implementation using `value` and `multiple`
return false;
};
const handleOptionClick = (option: O) => {
if (multiple) {
// Implementation for multiple selection using `onChange`
} else {
// Implementation for single selection using `onChange`
}
};
return (
<div>
{options.map((option) => {
const selected = isOptionSelected(option);
return (
<div key={option.id}>
{/* Render option UI */}
</div>
);
})}
</div>
);
};
注意: 泛型参数
<O, M>在组件上声明,然后传递给Props。
使用通用组件
1️⃣ 多选
export const UsageExample = () => {
const options: Option[] = [
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 3, name: "Third" },
];
return (
<SelectionExample
multiple
options={options}
onChange={(selected) => {
// `selected` is inferred as `Option[]`
console.log("selected:", selected);
}}
/>
);
};
2️⃣ 单选
export const SingleUsage = () => {
const options: Option[] = [
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 3, name: "Third" },
];
return (
<SelectionExample
options={options}
onChange={(selected) => {
// `selected` is inferred as `Option | undefined`
console.log("selected:", selected);
}}
/>
);
};
在这两种情况下,TypeScript 能够正确推断 value 的结构以及 onChange 的参数类型,从而为我们提供了想要的严格保证。
要点
- 条件泛型 让我们能够表达属性之间的关系(例如
multiple↔value的形状)。 - 将重复的条件逻辑提取为可复用的泛型类型(
Value)可以保持代码 DRY 且易读。 - 在组件本身上声明泛型并将其转发到 props 接口,可确保整个组件的类型安全。
使用这种模式,你可以构建高度可复用、类型安全的 React 组件,并根据 props 的取值自动调整其 API。祝编码愉快!
多选(附加示例)
export const UsageExample = () => {
return (
<>
{/* ... */}
{/* `values` is the selected options array */}
{console.log(values)}
</>
);
};
单选(附加示例)
export const UsageExample = () => {
return (
<>
{/* ... */}
{/* `value` is the selected option */}
{console.log(value)}
</>
);
};
错误用法
export const UsageExample = () => {
return (
<>
{/* ... */}
{/* `values` has type `Option | undefined` – this is a misuse when multiple is expected */}
{console.log(values)}
</>
);
};
我知道这篇文章可能会促使你更深入地探索 TypeScript 的特性。不过,我相信当你完成自己的类似实现并意识到组件变得更加强大时,你会感到非常满足。