禁止TypeScript中的记录索引类型转换



目前在TypeScript中可以写入:

const r: Record<string, string> = {
"42": "42_value"
};
console.log(r[42]);

打印:

"42_value"

我可以以某种方式禁止将数字作为索引传递给Record<string, string>索引运算符,并禁用numberstring的自动转换吗?或者我应该使用其他类型而不是Record来强烈指定密钥类型,并在console.log(r[42])行强制编译器出错?

借助额外的功能可以禁止:

type NotNumber<T> = keyof T extends `${number}` ? never : keyof T extends number ? never : T
const forbidNumber = <
Key extends string | number,
Dict extends Record<Key, string>
>(arg: NotNumber<Dict>) => arg
forbidNumber({ 42: 'hello' }) // error
forbidNumber({ '42': 'hello' }) // error
forbidNumber({ '42foo': 'hello' }) // ok

游乐场

正如你可能已经注意到的那样,它可能有一个缺点。这取决于你期待什么。如果你想禁止密钥中的任何数字字符,你可能不会使用这种递归类型:


type Numerical = number | `${number}`
type ReplaceNum<T, Cache extends string = ''> =
T extends ''
? Cache
: T extends `${infer Char}${infer Rest}`
? Char extends Numerical
? ReplaceNum<Rest, `${Cache}_`>
: ReplaceNum<Rest, `${Cache}${Char}`>
: never
type Test = ReplaceNum<'412foo'> // "___foo"
type ForbidNumbers<T extends Record<string | number, string>> = {
[Prop in keyof T as ReplaceNum<Prop>]: T[Prop]
}
const forbidNumber = <
Key extends string | number,
Dict extends Record<Key, string>
>(arg: Dict extends ForbidNumbers<Dict> ? Dict : never) => arg
forbidNumber({ 42: 'hello' }) // error
forbidNumber({ '42': 'hello' }) // error
forbidNumber({ '42foo': 'hello' }) // ok

游乐场

ReplaceNum-遍历对象键中的每个字符,并检查它是否为字符串化数字。如果它是一个数字,请将其替换为下划线_,否则保持原样。

ForbidNumbers-遍历每个键,并用返回类型ReplaceNum替换它。

Dict extends ForbidNumbers<Dict> ? Dict : never-如果Dict与调用ForbidNumbers后的对象相同-允许使用Dict,否则-返回never

你可以在我的文章中找到更多关于类型验证的信息,这里和这里


在typescript中没有类型否定。您只能在条件类型(如T extends number?never:T(的帮助下验证它。如果T满足条件,则返回never,否则返回T。把它当作一种替代品——相当令人生畏。因此,不可能像Record<not number, string>那样进行smth


我不喜欢的是,当我将数字传递给索引运算符时,它会自动转换为字符串

请参阅文档

索引签名属性类型必须是"string"或"number"。

可以同时支持这两种类型的索引器。。。可以同时支持这两种类型的索引器,但数字索引器返回的类型必须是字符串索引器返回类型的子类型。这是因为当使用number进行索引时,JavaScript实际上会在索引到对象之前将其转换为string。这意味着用100(number(进行索引与用"100"(string(进行索引是一样的,因此两者需要一致。

最新更新