模板文本键具有'any'类型,因为类型 'string' 的表达式不能用于索引类型



关于如何断言特定键具有特定接口或类型,还有许多类似的问题,就像这个问题一样。我的问题是不同的,因为所讨论的密钥是由两个部分组成的,而不是简单地从Object.keys中消费密钥。

这似乎与映射类型有关,但我甚至不知道这是否是TypeScript可以解决的问题。目前,我拥有的许多接口都充满了属性名,这些属性名是名称前缀(如team)和数字部分的可预测组合。这些数字总是在一些范围内,比如1-161-18。我已经添加了这些没有映射类型的接口,所以它们是非常大的接口,我希望以后使用映射类型。

我正在将文件逐渐从JavaScript转换为TypeScript。大多数新转换的TypeScript代码仍然不正确地类型为any类型。它涉及到前面范围的迭代,如下所示:

const name = record[`team${num}`]; // num is from 1-16

我知道record['team${num}']不是any,实际上是string的类型。如果我像record.team1;一样使用点表示法简单地写出几十个属性名,那么就不会出现错误,类型检查也能正常工作。简单的断言as stringas unknown as stringas (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而没有错误

相关内容