首页 > 解决方案 > 如何在 TypeScript 中创建具有最少强制属性的接口/类型?

问题描述

链接到最小可重现示例:https ://tsplay.dev/mL90eW

我有这个数据形状,我想在 TypeScript 中强制执行以下要求

const data: [
  { YearQtr: '2000Q1', House: '100', Unit: '200', Land: '300' },
  { YearQtr: '2000Q2',             Unit: '400'                },
  { YearQtr: '2000Q4', House: '500',            Land: '600'   },
  { YearQtr: '2001Q2',             Unit: '700', Land: '800'   },

我在 TypeScript 中为它定义了一个类型,但这也允许输入错误(参见下面示例中的最后三行:

type StackbarDatum = {
    [key: string]: string
}

const data: StackbarDatum[] = [
  { YearQtr: '2000Q1', House: '100', Unit: '200', Land: '300' },
  { YearQtr: '2000Q2',             Unit: '400'                },
  { YearQtr: '2000Q4', House: '500',            Land: '600'   },
  { YearQtr: '2001Q2',             Unit: '700', Land: '800'   },
  { YearQtr: '2002Q1', House: '900', Unit: '1000'             },
  {                                                           }, // this should not be allowed
  { YearQtr: '2002Q1'                                         }, // this should not be allowed
  { House: '900', Unit: '1000'                                }, // this should not be allowed
]

我想知道如何以类型安全的方式正确地做到这一点。

标签: typescript

解决方案


听起来你想要这样的联合类型:

type StackbarDatum = {
    YearQtr: string;
    House: string;
    Unit?: string;
    Land?: string;
} | {
    YearQtr: string;
    House?: string;
    Unit: string;
    Land?: string;
} | {
    YearQtr: string;
    House?: string;
    Unit?: string;
    Land: string;
}

游乐场链接


如果你有很多属性要强制它们选择 1 个或多个,那么这个联合可能会有点麻烦。在这种情况下,您可以使用映射类型来做类似的事情。例如(受这篇文章的启发):

type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]

type StackbarDatum = {
    YearQtr: string
} & AtLeastOne<{ 
    House: string;
    Unit: string;
    Land: string;
}>

游乐场链接


你谈论使这个通用。如果你的意思是你想要一个辅助类型来吐出这种格式的其他类型,那么像下面这样的东西可以工作:

type Datum<ValueLabel extends string, NameLabel extends string> = { 
    [K in ValueLabel]: string
} & AtLeastOne<{
    [K in NameLabel]: string
}>

type StackbarDatum = Datum<'YearQtr', 'House' | 'Unit' | 'Land'>;
type OtherDatum = Datum<'Foo', 'Money' | 'Time' | 'Mojo'>;

请注意,您仍然需要在编译时知道属性的名称,否则 typescript 无能为力。


推荐阅读