我刚刚从
javascript
转到typescript
,我正处于学习阶段。我有两种联合类型,我想从中创建一种类型。
我尝试了一切,
Exclude and Extract
也输入但没有运气。
下面是我的代码,包含预期结果和实际结果。
type Union1 = 'key1' | 'key2';
type Union2 = 'value1' | 'value2';
type MergeObj<K extends string | number, V> = {
[T in V as K]: T;
};
type Result = MergeObj<Union1, Union2>;
// expected: { key1: 'value1', key2: 'value2' }
// actual: { key1: 'value1' | 'value2', key2: 'value1' | 'value2' }
在实际情况中,我有两个混合类型的联合,如下
type Union1 = 'key1' | 'key2' | 'key3' | 4 | 'key5';
type Union2 = 'value1' | 'value2' | 3 | 'value4' | 'value5';
因此联合可以具有
string
或 number
并且联合将具有相同的价值始终。 IE。目前在示例中,我有 5 个。
所以我想从这两种联合类型转换为如下一种类型。
type Result = {
key1: 'value1';
key2: 'value2';
key3: 3;
4: 'value4';
key5: 'value5';
}
这本质上是不可能的,出于同样的原因,不可能将联合类型转换为元组类型(请参阅如何将联合类型转换为元组类型):TypeScript中的联合类型是无序的(或者更准确地说, ,他们的顺序是一个实现细节,不能依赖)。 类型
type U = 1 | 2
与 type U = 2 | 1
完全等效,编译器可以自由地将其中一种更改为另一种。 因此,通过“索引”或“位置”或“顺序”关联两个并集是不可能的,因为您无法区分 type Keys = "key1" | "key2"
和 type Keys = "key2" | "key1"
之间的区别。
如果你有键和值的元组,你可以迭代它们来获取你想要的对象。 事实上,我们可以通过梳理编译器确定的联合顺序的动作来实现您的
MergeObj
类型,如下所示:
// get the "Last" element in a union
type Last<T> =
((T extends any ? ((x: (() => T)) => void) : never) extends
((x: infer I) => void) ? I : never) extends () => infer U ? U : never
// convert a union to a tuple
type UnionToTuple<T, A extends any[] = []> =
[T] extends [never] ? A : UnionToTuple<Exclude<T, Last<T>>, [Last<T>, ...A]>
// merge key/value unions by first turning them to tuples
type MergeObj<K extends PropertyKey, V> =
[UnionToTuple<K>, UnionToTuple<V>] extends [infer KK extends PropertyKey[], infer VV extends any[]] ?
{ [P in keyof KK & keyof VV & `${number}` as KK[P]]: VV[P] } : never
我不会详细说明它是如何工作的,因为这不是重点。 我们来尝试一下:
// type Nums = 1 | 2 | 3 | 4 | 5; // 😇
type Union1 = 'key1' | 'key2' | 'key3' | 4 | 'key5';
type Union2 = 'value1' | 'value2' | 3 | 'value4' | 'value5';
type O = MergeObj<Union1, Union2>
/* type O = {
key1: "value1";
key2: "value2";
key3: 3;
4: "value4";
key5: "value5";
} */
嘿,这完全符合你的要求,对吧? 那么问题出在哪里呢? 好吧,如果你看一下注释掉的行
// type Nums = 1 | 2 | 3 | 4 | 5; // 😇
如果我们取消注释该行,就会发生一些事情:
type Nums = 1 | 2 | 3 | 4 | 5; // 😈
type Union1 = 'key1' | 'key2' | 'key3' | 4 | 'key5';
type Union2 = 'value1' | 'value2' | 3 | 'value4' | 'value5';
type O = MergeObj<Union1, Union2>
/* type O = {
4: 3;
key1: "value1";
key2: "value2";
key3: "value4";
key5: "value5";
} */
生成的对象类型已发生更改,这仅仅是由于看似不相关的代码的更改。 编译器可以自由地以它喜欢的方式和顺序来表示联合。 事实证明,通过在代码中比定义
1 | 2 | 3 | 4 | 5
和 Union1
之前更早地显式引用 Union2
,编译器将倾向于在遇到的任何未来联合中首先列出这些:
// type Union1 = 4 | "key1" | "key2" | "key3" | "key5"
// type Union2 = 3 | "value1" | "value2" | "value4" | "value5"
这会毁了一切。 在这里继续下去没有什么意义。
回顾一下:根本没有办法将
Union1
和 Union2
定义为并集,然后根据“索引”、“位置”或“顺序”将它们可靠地关联起来。 您应该找到一种完全不同的方法,例如在元组等有序数据结构中记录所需的顺序;或者从所需的对象开始并从中导出并集;或完全放弃,具体取决于您的用例。
export type colorsType = 'primary' | 'info' | 'secondary' | 'warning' | 'danger' | 'light' | 'dark'
export type btnColorsType =
| colorsType
| 'outline-primary'
| 'outline-info'
| 'outline-secondary'
| 'outline-warning'
| 'outline-danger'
| 'outline-light'
| 'outline-dark'
新类型 btnColorsType 将具有前 2 个类型的全部值👍