关于如何断言特定键具有特定接口或类型,还有许多类似的问题,就像这个问题一样。我的问题是不同的,因为所讨论的密钥是由两个部分组成的,而不是简单地从Object.keys
中消费密钥。
这似乎与映射类型有关,但我甚至不知道这是否是TypeScript可以解决的问题。目前,我拥有的许多接口都充满了属性名,这些属性名是名称前缀(如team
)和数字部分的可预测组合。这些数字总是在一些范围内,比如1-16
或1-18
。我已经添加了这些没有映射类型的接口,所以它们是非常大的接口,我希望以后使用映射类型。
我正在将文件逐渐从JavaScript转换为TypeScript。大多数新转换的TypeScript代码仍然不正确地类型为any
类型。它涉及到前面范围的迭代,如下所示:
const name = record[`team${num}`]; // num is from 1-16
我知道record['team${num}']
不是any
,实际上是string
的类型。如果我像record.team1;
一样使用点表示法简单地写出几十个属性名,那么就不会出现错误,类型检查也能正常工作。简单的断言as string
、as unknown as string
和as (typeof record[keyof typeof record])
都没有从有问题的代码中删除错误消息。我现在知道如何使用类型断言来抑制类型错误,但是注意,代码会导致一个错误,并且当它实际上是一个运行时错误时,现在没有错误。
是否有一个习惯的方法来检查类型使用TypeScript?我不希望出现误差为1的错误。我的一些代码可以重写来使用现有的属性,而其他代码则从这些现有的属性中创建新的属性,这些属性是用名称和数字的组合索引的。
您可以使用已知的模板文字类型。只要知道球队的名字和人数就行。一个可能的解决方案是这样的:
type TeamNumber = 1 | 2
type TeamName = 'TeamA' | 'TeamB'
type TeamData = { trainer?: string }
// here is where the magic happens:
// we compute all keys based on other union types inside a template string
type TeamRecord = {
[K in `${TeamName}${TeamNumber}`]: TeamData;
};
// you get autocompletion for all properties
const teamRecord: TeamRecord = {
TeamA1: {},
TeamA2: {},
TeamB1: {},
TeamB2: {}
}
请随意使用这个TypescriptPlayground。
编辑:
您也可以创建TeamNumber如果需要遍历TeamNumber的值,则从数组中获取类型。。因此,不需要const max
变量来防止迭代中出现假值。
const teamNumbers = [1, 2, 3, 4] as const;
type TeamNumber = typeof teamNumbers[number];
type Team = 'team';
type VerboseTeamRecord = Teams & {
id: number;
event: string;
gender: 'M' | 'F' | 'B';
};
type Teams = {
[K in `${Team}${TeamNumber}`]: string;
};
let verboseTeamRecord: Teams = { team1: 'A', team2: 'B', team3: 'C', team4: 'D' };
teamNumbers.forEach(teamNumber => {
const k = `team${teamNumber}` as keyof Teams;
console.log(verboseTeamRecord[k]);
});
这是编辑的Typescriptplayground。
既然您的TeamRange
类型是数值文字类型的联合,那么您只需在模板文字字符串后使用const
断言即可获得所需的行为:
const idx = `team${num}` as const;
/* const idx: "team1" | "team2" | "team3" | "team4" | "team5" | "team6" |
"team7" | "team8" | "team9" | "team10" | "team11" | "team12" |
"team13" | "team14" | "team15" | "team16" */
编译器然后将你的模板字面值字符串解释为具有模板字面值类型,它自动将数字字面值转换为字符串字面值并为你连接字符串。所以一切都按照你的想法进行:
const name = record[`team${num}` as const]; // string, not any
name.toUpperCase(); // okay
name.blargh; // error
请注意,将会有一个特性,即像team${num}
这样的模板文字字符串将自动推断为具有模板文字类型而不是string
,因此您不需要编写as const
。这在microsoft/TypeScript#41891中实现过。
但事实证明,这破坏了现有的现实世界的代码库(特别是谷歌,见microsoft/TypeScript#42593),被认为太奇怪了(见microsoft/TypeScript#42589的讨论),最终在microsoft/TypeScript#42588中恢复。
所以现在,如果你想让编译器把模板文字字符串看作是模板文字类型,你需要通过添加const
断言来告诉它。
Playground链接到代码
虽然不完美,但有一种方法可以利用一些类型检查,甚至在模板文字属性键上。TypeScript的类型检查器检查代码的程度是有限的。只要代码使用模板文字,TypeScript就没有办法执行自动的类型检查,但是有机会使用比简单的类型断言更多的东西,通过将模板文字的每个部分的类型断言为预期的相同类型。下面是一个例子:
type TeamNumber = 1 | 2 | 3 | 4;
type Team = 'team';
type VerboseTeamRecord = Teams & {
id: number;
event: string;
gender: 'M' | 'F' | 'B';
};
type Teams = {
[K in `${Team}${TeamNumber}`]: string;
};
var verboseTeamRecord: VerboseTeamRecord = { team1: 'A', team2: 'B', team3: 'C', team4: 'D', id: 1, event: 'foo', gender: 'M', };
const max: TeamNumber = 4;
const prefix: Team = 'team';
for (let i: TeamNumber = 1; i <= max; i++) {
console.log(verboseTeamRecord[`${prefix}${i}` as keyof Teams]);
}
在这种代码模式下,有些错误是不可能发生的。虽然max
可以简单地替换为5来创建错误,但max
不能在运行时之前替换为5而没有错误。