推断地图中键的类型(同时在地图中键入值)

问题描述 投票:1回答:1

我想从地图上推断出钥匙的类型。

我可以成功做到这一点:

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;

有没有解决的办法? (无需手动写出地图键。)

typescript type-inference
1个回答
0
投票

在这种情况下,我建议使用一个辅助函数,该函数不更改componentStyles的推断类型,但仅允许您制作具有正确属性类型的对象:

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

这是generic函数,其中类型参数Tconstrained,可分配给Record<keyof T, Style>。这是一个自引用约束(称为F-bounded polymorphism),允许编译器验证从调用函数推断出的类型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将采用对象文字并将其赋予implicit index signature

但是如果您要使用接口值,因为they are not currently allowed to gain implicit index signatures,我不建议这样做,而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'.

不确定接口值的东西是否属于您的用例。


无论如何,希望能有所帮助;祝你好运!

Link to code

© www.soinside.com 2019 - 2024. All rights reserved.