typescript - 为什么我不能在 const 数组中创建一种有效索引?
问题描述
考虑以下:
const STATES = ["Todo", "In Progress", "Blocked", "Done"] as const;
type State = typeof STATES[number]; // "Todo" | "In Progress" | "Blocked" | "Done"
type StateIndex = keyof typeof STATES; // number | keyof STATES
// so this works:
let goodIndex: StateIndex = 0;
// but so does this
let badIndex : StateIndex = 42;
在这个例子中, TS 编译器完全知道 STATES,所以它的长度也是 (4)。那么为什么StateIndex
评估的类型为
number | keyof STATES
并不是
0 | 1 | 2 | 3
?
我知道可以以其他方式定义类型:
type StateIndex = 0 | 1 | 2 | 3
type State = typeof States[StateIndex]
但是编译器是否没有对有效索引的文字类型联合进行评估所需的所有信息?还有另一种方法可以将类型系统推向正确的答案吗?
解决方案
typeof STATE
实际上是一个 ReadonlyArray。
ReadonlyArray
反过来有这个接口:
interface ReadonlyArray<T> {
readonly length: number;
readonly [n: number]: T;
// other methods
}
所以,我们知道通用类型number
可以作为不可变数组/元组的索引。
让我们看一下 的所有键STATES
:
const STATES = ["Todo", "In Progress", "Blocked", "Done"] as const;
type STATES = typeof STATES
type State = STATES[number]; // "Todo" | "In Progress" | "Blocked" | "Done"
type StateIndex = keyof (typeof STATES); // number | keyof STATES
type StateKeys = {
[Prop in StateIndex]: Prop
}
您可能已经注意到,除了预期的键之外0 | 1 | 2 | 3
,STATES
还有其他键。
因此,让我们尝试提取所有number
密钥:
const STATES = ["Todo", "In Progress", "Blocked", "Done"] as const;
type STATES = typeof STATES
type State = STATES[number];
type StateIndex = keyof (typeof STATES);
type FilterNumbers<T extends PropertyKey> = T extends `${number}` ? T : never
// "0" | "1" | "2" | "3"
type Indexes = FilterNumbers<StateIndex>
我们几乎做到了。但是由于某种原因,TypeScript 返回字符串化的键而不是数值。
可以以通用方式将它们转换为数字。
type MAXIMUM_ALLOWED_BOUNDARY = 999
type ComputeRange<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: ComputeRange<N, [...Result, Result['length']]>
)
type NumberRange = ComputeRange<MAXIMUM_ALLOWED_BOUNDARY>[number]
type FilterNumbers<T extends PropertyKey> = T extends `${number}` ? T : never
type ToNumber<T extends string, Range extends number> =
(T extends string
? (Range extends number
? (T extends `${Range}`
? Range
: never)
: never)
: never)
const STATES = ["Todo", "In Progress", "Blocked", "Done"] as const;
type STATES = typeof STATES
type State = STATES[number];
type GetNumericKeys<T extends PropertyKey> = ToNumber<FilterNumbers<T>, NumberRange>
// 0 | 1 | 2 | 3
type Result = GetNumericKeys<keyof STATES>
您可以在此问题/答案和/或我的文章中找到更多上下文和解释
还有另一种方法可以从元组中获取允许的索引。
const STATES = ["Todo", "In Progress", "Blocked", "Done"] as const;
type STATES = typeof STATES
type State = STATES[number];
type AllowedIndexes<
Tuple extends ReadonlyArray<any>,
Keys extends number = never
> =
(Tuple extends readonly []
? Keys : (Tuple extends readonly [infer _, ...infer Tail]
? AllowedIndexes<Tail, Keys | Tail['length']>
: Keys
)
)
// 0 | 3 | 2 | 1
type Keys = AllowedIndexes<STATES>
您只需反复迭代一个元组并将当前元组的长度推送到 Keys
联合
虽然第一个示例可能看起来像是过度设计问题,但它在其他用例中可能会有所帮助。
只是想展示不同的方式。
推荐阅读
- reactjs - 有效载荷未定义
- visual-studio - 在不再存在/已重命名的文件中构建错误
- xpath - 具有持久 URL 的多个表选项卡
- javascript - react-dates DayPickerRangeController,如果用户在选择时单击它,是否有办法清除 startDate 或 endDate?
- c# - 为什么在 C# 中实现 ICustomFormatter 接口时需要执行格式类型检查?
- python - 如何根据条件在 pandas 数据帧上应用字符串拆分方法?
- c# - 我可以在 IValueConverter 中使用模型类吗?
- java - S3上传String的用户元数据列表
- google-analytics - 是否可以通过谷歌分析找出来自特定州的人在我的网站上搜索的内容?
- splunk - 没有年份指标的事件将如何被索引?