如何在推断值的同时约束TypeScript对象的键?

时间:2019-03-13 17:58:32

标签: typescript

通常,当我在TypeScript中具有字符串文字的并集时,我想创建一个从字符串到其他值的映射。这涉及创建一个不允许值推断的类型,这在我也要访问值类型时可能会很烦人。例如,这是一个基本的挂断:

type UserPersona =
    | "entrepreneur"
    | "programmer"
    | "designer"
    | "product_manager"
    | "marketing_sales"
    | "customer_support"
    | "operations_hr"


const userPersonaDisplayNames: { [key in UserPersona]: string } = {
    entrepreneur: "Entrepreneur",
    programmer: "Programmer",
    designer: "Designer",
    product_manager: "Product Manager",
    marketing_sales: "Marketing & Sales",
    customer_support: "Customer Support",
    operations_hr: "Operations & HR",
}

这里的问题是userPersonaDisplayNames的值是string类型,而不是其文字值。

当我离开类型时,键和和值是文字类型,这很不错,但是键不再受联合类型的约束。

const userPersonaDisplayNames = {
    entrepreneur: "Entrepreneur",
    programmer: "Programmer",
    designer: "Designer",
    product_manager: "Product Manager",
    marketing_sales: "Marketing & Sales",
    customer_support: "Customer Support",
    operations_hr: "Operations & HR",
}

我发现的一种破解方法是使用扩展来断言。

type Assert<A,B> = A extends B

type assertPersonaKeys = Assert<keyof userPersonaDisplayNames, UserPersona>

这行得通,但是有点毛病。我周围有TSLint抱怨的未使用类型。

似乎infer关键字正是我在寻找的关键字,但是我不确定它如何工作,在我的情况下它不起作用。理想情况下,我可以做这样的事情:

const userPersonaDisplayNames: { [key in UserPersona]: infer } = {
    entrepreneur: "Entrepreneur",
    programmer: "Programmer",
    designer: "Designer",
    product_manager: "Product Manager",
    marketing_sales: "Marketing & Sales",
    customer_support: "Customer Support",
    operations_hr: "Operations & HR",
}

有什么想法可以彻底解决这个问题吗?

1 个答案:

答案 0 :(得分:2)

使用辅助函数来表示要查看的约束,然后调用它吗?因为它调用(t => t)(obj)而不是仅使用obj,所以运行时开销非常小。

const asUserPersonaDisplayNames = <
  S extends string, // allow S to be inferred as a string literal
  T extends Record<UserPersona, S> & // require keys from UserPersona
  Record<Exclude<keyof T, UserPersona>, never> // disallow keys not from UserPersona
>(t: T) => t

const userPersonaDisplayNames = asUserPersonaDisplayNames({
  entrepreneur: "Entrepreneur",
  programmer: "Programmer",
  designer: "Designer",
  product_manager: "Product Manager",
  marketing_sales: "Marketing & Sales",
  customer_support: "Customer Support",
  operations_hr: "Operations & HR",
}); // type has fully string-literal values

const missing = asUserPersonaDisplayNames({
  entrepreneur: "Entrepreneur",
  programmer: "Programmer",
  product_manager: "Product Manager",
  marketing_sales: "Marketing & Sales",
  customer_support: "Customer Support",
  operations_hr: "Operations & HR",
}) // error, property "designer" is missing

const extra = asUserPersonaDisplayNames({
  candlestick_maker: "Rub a Dub Dub",
  entrepreneur: "Entrepreneur",
  programmer: "Programmer",
  designer: "Designer",
  product_manager: "Product Manager",
  marketing_sales: "Marketing & Sales",
  customer_support: "Customer Support",
  operations_hr: "Operations & HR",
}); // error, property "candlestick_maker" is extra

const notString = asUserPersonaDisplayNames({
  entrepreneur: 25,
  programmer: "Programmer",
  designer: "Designer",
  product_manager: "Product Manager",
  marketing_sales: "Marketing & Sales",
  customer_support: "Customer Support",
  operations_hr: "Operations & HR",
}); // error, number is not expected

希望有所帮助;祝你好运!