Определение типа ключей на карте (а также ввод значений на карте) - PullRequest
1 голос
/ 07 октября 2019

Я хотел бы определить тип ключей на карте.

Я могу успешно сделать это:

const componentStyles = {
  button: { color: 'red' },
  heading: { fontSize: 18, lineHeight: 28 },
  body: { fontSize: 12, lineHeight: 18 },
};

type ComponentName = keyof (typeof componentStyles);

И TypeScript выведет, что:

type ComponentName = 'button' | 'heading' | 'body';

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

interface Style {
  color?: string;
  fontSize?: number;
  lineHeight?: number;
}

const componentStyles: {[key: string]: Style} = {
  button: { color: 'red' },
  heading: { fontSize: 18, lineHeight: 28 },
  body: { fontSize: 12, lineHeight: 18 },
};

type ComponentName = keyof (typeof componentStyles);

Тогда TypeScript выведет:

type ComponentName = string | number;

Есть ли способ обойти это? (Без записи ключей карты вручную.)

1 Ответ

1 голос
/ 07 октября 2019

Для этого случая использования я бы порекомендовал вспомогательную функцию, которая не изменяет предполагаемый тип componentStyles, но который позволит вам только создавать объекты с правильными типами свойств:

const asComponentStyles = <T extends Record<keyof T, Style>>(t: T) => t;

Этоявляется универсальной функцией, где параметр типа T имеет значение с ограничением , которое можно назначить на Record<keyof T, Style>. Это само-ссылочное ограничение (известное как F-ограниченный полиморфизм ) и позволяет компилятору проверять, что тип T, определенный из вызова функции, имеет для любого ключа в keyof T свойства, назначаемыедо Style. Давайте посмотрим на это в действии:

const componentStyles = asComponentStyles({
    button: { color: 'red' },
    heading: { fontSize: 18, lineHeight: 28 },
    body: { fontSize: 12, lineHeight: 18 },
});

type ComponentName = keyof (typeof componentStyles);
// type ComponentName = "button" | "heading" | "body"

Это работает, как вы и планировали. И давайте удостоверимся, что он делает свою работу по предотвращению плохих Style свойств:

const errorChecking = asComponentStyles({
    okay: { color: 'chartreuse', lineHeight: 123 },
    badColor: { color: 123, lineHeight: 123 }, // error!
    //          ~~~~~ <── 'number' is not assignable to type 'string | undefined'.
    excessPropChecking: { colour: "grey" } // error!
    //   ┌──────────────> ~~~~~~~~~~~~~~
    // Object literal may only specify known properties, 
    // but 'colour' does not exist in type 'Style'.
    // Did you mean to write 'color'?
})

Также хорошо выглядит.


Обратите внимание, что вы можете избежать неприятностей сболее простая, не относящаяся к самому себе вспомогательная функция, использующая сигнатуру индекса, аналогичную той, которую вы делаете в своем вопросе:

const asComponentStylesIndex = <T extends { [k: string]: Style }>(t: T) => t;

const componentStylesIndex = asComponentStylesIndex({
    button: { color: 'red' },
    heading: { fontSize: 18, lineHeight: 28 },
    body: { fontSize: 12, lineHeight: 18 },
}); // okay

Это работает, потому что TypeScript возьмет литерал объекта и даст ему неявное подпись индекса .

Но я бы не рекомендовал это, если вы собираетесь использовать значения интерфейса, поскольку в настоящее время им не разрешено получать неявные подписи индекса , в то время как F-ограниченная версия работает для интерфейсова также литералы объектов:

interface MyComponent {
    foo: Style,
    bar: Style
}
declare const myComponent: MyComponent; // value of an interface type

const works = asComponentStyles(myComponent); // okay
const doesntWork = asComponentStylesIndex(myComponent); // error!
//  ┌───────────────────────────────────> ~~~~~~~~~~~
// Index signature is missing in type 'MyComponent'.

Не уверен, являются ли вещи с интерфейсом частью вашего варианта использования или нет.


В любом случае, надеюсь, это поможет;удачи!

Ссылка на код

...