Как ограничить ключи объекта TypeScript при выводе значений?

#typescript

#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 amp; Sales",
    customer_support: "Customer Support",
    operations_hr: "Operations amp; HR",
}
  

Проблема здесь в том, что значения userPersonaDisplayNames являются типом string , а не их буквальными значениями.

Когда я оставляю тип, ключи и значения являются литеральными типами, что замечательно, но ключи больше не ограничены типом объединения.

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

Один из обнаруженных мной способов заключается в использовании extends для assert .

 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 amp; Sales",
    customer_support: "Customer Support",
    operations_hr: "Operations amp; HR",
}
  

Есть идеи, как решить эту проблему простым способом?

Ответ №1:

Используйте вспомогательную функцию для представления ограничения, которое вы хотите увидеть, а затем вызовите его? Накладные расходы во время выполнения очень малы, поскольку он вызывается (t => t)(obj) вместо простого использования obj .

 const asUserPersonaDisplayNames = <
  S extends string, // allow S to be inferred as a string literal
  T extends Record<UserPersona, S> amp; // 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 amp; Sales",
  customer_support: "Customer Support",
  operations_hr: "Operations amp; HR",
}); // type has fully string-literal values

const missing = asUserPersonaDisplayNames({
  entrepreneur: "Entrepreneur",
  programmer: "Programmer",
  product_manager: "Product Manager",
  marketing_sales: "Marketing amp; Sales",
  customer_support: "Customer Support",
  operations_hr: "Operations amp; 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 amp; Sales",
  customer_support: "Customer Support",
  operations_hr: "Operations amp; HR",
}); // error, property "candlestick_maker" is extra

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

Надеюсь, это поможет; удачи!

Комментарии:

1. Вы уже написали книгу по TS? Я бы купил это в мгновение ока.