从字符串列表 a 中导出类型 A

Derive a type A from a list of strings a

假设我有一个常量且不可变的字符串列表a

const a = ['b', 'c', 'd']

我想创建一个如下所示的类型 A:

type A = 'b' | 'c' | 'd'

我的问题是:如何从列表 a 中自动创建类型 A

另一种方法也适用于我:How to create the list a given the type A

你想做的事情是不可能的,因为数组是在运行时创建的,并且编译器无法确定对数组的更改。因此,它无法强制执行类型系统。

例如,考虑以下内容:

const array = [ "Bob", "James", "Dan" ];
let value: *A key of array*
setTimeout(() => {
    value = "James";
}, 1000);
array.splice(1, 1);

在上面的场景中会发生什么?调用 value = "James 时,"James" 已从数组中删除,因此应该抛出错误!但这发生在运行时,因此编译器无法警告您。

解决方法是使用以下结构声明一个类型,但不能对其进行修改:

type MyType = "Bob" | "James" | "Dan"
let value: MyType = "Dan";
value = "Steven" // <- Compiler error!

编辑: 在他们的评论中快速总结。

是的,你可以做到这一点。唯一的障碍是如果你写

const a = ['b','c','d'];
// inferred as string[]

然后 a 将被推断为 string[],因此在您尝试时忘记了文字值 'a''b''c'导出 A。您始终可以显式注释 a 的类型:

const a: ['b','c','d'] = ['b','c','d'];
type A = typeof a[number];
//type A = "b" | "c" | "d"

但现在你在重复你自己,你正在努力避免。有解决办法:


I recommended a helper function (tuple() in tuple.ts) 中推断元组类型。

const a = tuple('b','c','d');
//inferred as type ["b", "c", "d"]

type A = typeof a[number]; 
//type A = "b" | "c" | "d"

如果你不关心它是一个元组,你可以写一个简单的数组推理辅助函数来代替:

function inferLiteralArray<T extends string>(...arr: T[]): T[] {
  return arr;
}

const a = inferLiteralArray("b", "c", "d");
//inferred as type ("b" | "c" | "d")[]

type A = typeof a[number];
//type A = "b" | "c" | "d"

两种方法都行,您无需重复。希望有所帮助;祝你好运!


更新 1

@fritz_da_silva :

awesome, it works neatly!!! the a[number] syntax looks like black magic to me. could u please elaborate on why it works?

当然可以。语法应该这样解释(为了清楚起见,请参阅我添加的括号):

type A = (typeof a)[number];

typeof a 部分使用 typeof type query operator 获取 a 的类型,(对于 tuple() 示例)是 ["b", "c", "d"]

那我现在用的是indexed access operator。如果你有一个类型 T 和它的 keys 之一的类型 K(所以它应该扩展 keyof T;你可以使用像这样的字符串文字"length" 或像 0 这样的数字文字或这些的并集),那么 T[K] 就是该键可访问的 属性 的类型。对于类似数组的类型 A,类型 A[number] 获取该数组类型的元素的类型。在元组示例中,这证明是一个联合,因为有多个 number 类型的键具有不同的 属性 类型。 (当您使用索引访问运算符时,T[K1|K2] 应该等于 T[K1]|T[K2])。观察:

type TheTuple = typeof a; // ['b','c','d'];
type Zeroth = TheTuple[0]; // 'b'
type First = TheTuple[1]; // 'c'
type Second = TheTuple[2]; // 'd'
type A = TheTuple[number]; // 'b'|'c'|'d'

这有意义吗?