typescript - TypeScript:按类型属性选择数组的元素
问题描述
在 TypeScript 项目中,我有一个容器数组,其中包含一个type
属性和一些附加数据,具体取决于它们的类型。
type Container<Type extends string> = {
type: Type;
}
type AContainer = Container<"a"> & {
dataA: number;
}
type BContainer = Container<"b"> & {
dataB: boolean;
}
const data: (AContainer | BContainer)[] = [
{ type: "a", dataA: 17 },
{ type: "b", dataB: true }
];
我的目标是编写一个函数,允许我通过它从该数组中选择一个元素,type
并且具有完全的类型安全性。像这样的东西:
const getByType = <T extends string>(data: Container<string>[], type: T): Container<T> => {
for (const c of data) {
if (c.type === type) return c;
}
throw new Error(`No element of type ${type} found.`);
};
const dataA: AContainer = getByType(data, "a");
问题是试图让 TypeScript 相信该函数是类型安全的,并且返回值是原始数组的一个元素并且具有请求的类型。
这是我最好的尝试:
const getByType = <ContainerType extends Container<string>, Type extends string>(data: (ContainerType & Container<string>)[], type: Type): ContainerType & Container<Type> => {
for (const c of data) {
if (c.type === type) return c;
}
throw new Error(`No element of type ${type} found.`);
};
但是,TypeScript 既不理解比较c.type === type
确保 aContainer<string>
变成 a Container<Type>
,也不理解示例调用的返回类型 ,由于 中的冲突而AContainer | (Container<"b"> & { dataB: boolean; } & Container<"a">)
等于。第一个问题可以通过在下面的代码块中使用类型谓词作为一个类型谓词来解决(虽然那种感觉像作弊),但我还没有找到第二个问题的解决方案。AContainer
Container<"b"> & Container<"a">
const isContainer = <Type extends string>(c: Container<string>, type: Type): c is Container<Type> => {
return typeof c === "object" && c.type === type;
};
有什么办法可以让它工作吗?如果它本身和它的使用都是类型安全的,我更喜欢它getByType
,但如果那不可能,我希望至少使用getByType
不需要任何不安全的类型断言。
我可以更改容器类型的定义,但实际数据是固定的。(对于背景:xml2js XML 解析器。)
解决方案
我们可以使用infer
andExtract
来达到目的。考虑:
const getByType = <ContainerType extends Container<string>, Type extends ContainerType extends Container<infer T> ? T : never, Chosen extends Type>(data: ContainerType[], type: Chosen) => {
for (const c of data) {
if (c.type === type) return c as Extract<ContainerType, {type: Chosen}>;
}
throw new Error(`No element of type ${type} found.`);
};
const containerA: AContainer = {
dataA: 1,
type: "a"
}
const containerB: BContainer = {
dataB: true,
type: "b"
}
const b = getByType([containerB, containerA], 'b')
// b is infered as BContainer
需要注意的几点:
type: ContainerType extends Container<infer T> ? T : never
我们说参数需要在给定数组中包含确切的可用类型Extract<ContainerType, {type: Chosen}>
我们说我们返回联合的元素,{type: Chosen}
它意味着具有这种确切类型的成员
我们在第二个参数上也有严格类型,在示例中缩小为a | b
推荐阅读
- javascript - API 日出/日落 - 从 UTC 转换为本地时间
- python - PyTorch jit 跟踪不返回命名元组作为输出
- python - 从一个随机数列表中,如何识别五个连续的数字并将它们分开在一个新的列表中?
- c++ - 为 C++ 项目创建 makefile
- javascript - 我可以添加编辑选项并使用 javascript 赋予它功能吗
- css - 屏幕变小时gridview没有响应
- r - 如何将这些 JSON 数据转换为 R 中的数据框
- python - 如何在 OpenCV 中为这些图像获取这些 ROI
- express - 暂停所有请求并锁定 express api?
- pine-script - 在 pine-script 如何根据条件设置高点和低点?