如何在 TypeScript 中安全地键入分层元数据对象?



我想创建一些常量来定义具有子类别的类别层次结构。然后,这些将被重用于在应用程序中呈现不同的页面,但是通过将它们定义为一个位置的常量,我可以快速搭建页面基架。

例如,给定的类别如下:

const CATEGORIES = [
{
id: 'diet',
name: 'Diet',
color: 'green',
},
{
id: 'energy',
name: 'Energy',
color: 'yellow',
},
...
]

但是现在我想用TypeScript键入这些对象。我可以使用as const语法来推断类别类型:

{
id: 'diet' | 'energy' | ...
name: 'Diet' | 'Energy' | ...
color: 'green' | 'yellow' | ...
}

这很好。(虽然我真的不需要name/color特异性。

但是在定义子类别时,我遇到了一个问题......

const SUBCATEGORIES = [
{
id: 'red_meat',
category: 'diet',
name: 'Red Meat',
emoji: '🥩',
},
...
]

如果我在SUBCATEGORIES对象上使用as const,那么category键就没有类型安全。我很容易拼错'deit',永远不知道。

但是如果我使用这样的声明:

const SUBCATEGORIES: Array<{
id: string
category: typeof CATEGORIES[number]['id']
...
}> = [
...
]

然后我以后无法再从子类别推断出id键字符串,因为它们将具有一种string类型。


有没有办法在TypeScript中定义这些类型的元数据结构,以便在as const和定义的类型之间有一些中间地带?如何在键入其余键时将id键推断为常量?

我愿意将它们定义为对象而不是数组,但我似乎无法找到一种有帮助的方法。

在这种情况下,我通常会创建一个帮助函数,该函数验证值是否可以分配给类型,而无需将其扩大到该类型。 假设CATEGORIES看起来像

const CATEGORIES = [
{
id: 'diet',
name: 'Diet',
color: 'green',
},
{
id: 'energy',
name: 'Energy',
color: 'yellow',
},
// ...
] as const;

那么帮助程序函数asSubcategories可能如下所示:

const asSubcategories = <S extends ReadonlyArray<{
id: string;
category: typeof CATEGORIES[number]['id'];
name: string;
emoji: string;
}>>(s: S) => s;

在这里asSubcategories实际上只是返回其输入而不更改它,但它只接受可分配给您关心的类型S的泛型类型参数。 然后你这样称呼它:

const SUBCATEGORIES = asSubcategories([
{
id: 'red_meat',
category: 'diet',
name: 'Red Meat',
emoji: '🥩',
},
//...
] as const);

由于没有错误,您没有拼错category,并且SUBCATEGORIES的类型仍然是

/* const SUBCATEGORIES: readonly [{
readonly id: "red_meat";
readonly category: "diet";
readonly name: "Red Meat";
readonly emoji: "🥩";
}] */

如果你错了东西,你应该得到一个大错误:

const BADSUBCATEGORIES = asSubcategories([
{
id: 'read_met',
category: 'deit',
name: 'Read Met',
emoji: '🍔',
},
//...
] as const); // error!!
/* Types of property 'category' are incompatible.
Type '"deit"' is not assignable to type '"diet" | "energy"'
*/

耶! 有一些方法可以使用帮助程序函数来缩小某些属性的范围,同时扩大其他属性,但希望上述内容足以帮助您继续操作。祝你好运!

操场链接到代码

最新更新