英文:
How does TS type intersection narrow down types?
问题
我有一个`queryObject`,我可以使用TS映射类型以及实用类型`Uncapitalize`来构建,如下所示:
```typescript
type Params = {
ParamOne: string;
ParamTwo: string;
};
type Query<T> = {
[K in keyof T]: `&${Uncapitalize<K>}=`;
};
const queryObject: Query<Params> = {
ParamOne: '¶mOne=',
ParamTwo: '¶mTwo=',
};
当然,TS不允许我在任何泛型类型T
上使用Uncapitalize
,所以我可以使用extends
进行类型缩小,以检查类型是否为string
,如下所示:
type Query<T> = {
[K in keyof T]: K extends string ? `&${Uncapitalize<K>}=` : never;
};
它可以正常工作,但最近我遇到了一种替代方法,我无法完全理解:
type Query<T> = {
[K in keyof T]: `&${Uncapitalize<K & string>}=`;
};
我不明白在这种情况下类型交集如何缩小K
类型,我运行了一些测试,似乎它的工作方式与extends
版本完全相同。
问题:
它是如何工作的?交集不应该只用于合并多个记录的属性吗?是否有官方解释(我找不到)?此语法是否有任何注意事项?
<details>
<summary>英文:</summary>
I have a `queryObject` that I can build using TS mapped types along with utility type `Uncapitalize` like this:
type Params = {
ParamOne: string;
ParamTwo: string;
};
type Query<T> = {
[K in keyof T]: &${Uncapitalize<K>}=
;
};
const queryObject: Query<Params> = {
ParamOne: '¶mOne=',
ParamTwo: '¶mTwo=',
};
Of course TS won't let me use `Uncapitalize` on any generic type `T`, so I can do type narrowing using `extends` to check if the type is `string` like so:
type Query<T> = {
[K in keyof T]: K extends string ? &${Uncapitalize<K>}=
: never;
};
It works fine, but recently I came across an alternative which I can't quite understand
type Query<T> = {
[K in keyof T]: &${Uncapitalize<K & string>}=
;
};
I don't get how type intersection narrows `K` type in that case, I've run some tests and it seems to work exactly like `extends` version.
**Question:**
How does it work? Shouldn't intersection only be used for combining several records properties? Is there an official explanation for this (I couldn't find one)? Any caveats for this syntax?
</details>
# 答案1
**得分**: 1
你可以将每种类型解释为某个数学集合。
集合中的交集是共同元素的集合。
联合是两个集合的所有元素的组合(A中不在B中的元素,A和B的交集,以及B中不在A中的元素)。
与空集合的任何交集都会得到空集合作为结果。在TypeScript中,空集合是 `never`。因此,以下交集将导致 `never`:
```typescript
type Case1 = string & never // never
type Case2 = string[] & never // never
type Case3 = Record<string, unknown> & never // never
与 never
的相反联合将导致另一个集合:
type Case1 = string | never // string
type Case2 = string[] | never // string[]
type Case3 = Record<string, unknown> | never // Record<string, unknown>
任何字符串字面量都是 string
的子集,因此与 string
的交集将导致字符串字面量本身,但与联合的交集将仅导致字符串:
type Case1 = "literal" | string // string
type Case2 = "literal" & string // "literal"
由于对象键具有 PropertyKey
类型,即 string | number | symbol
,keyof Type
返回此类型,如果您需要提取只有字符串键的键,有两种方法可以做到:
{[K in Extract<keyof Type, string>]: {}}
或
{[K in keyof Type & string]: {}}
第二种情况的示例:
type K = 'str' | 1 | number | '2';
type OnlyStringKeys = K & string // 'str' | '2'
由于 1
、number
和 string
之间没有交集,它们将导致空集合(never
)。
在交叉两个对象的情况下,情况有些不同,这是TypeScript有意为之的行为。发生这种情况的原因是编译器无法简化类型,就像它可以处理原始类型(例如字符串)一样。交叉两个对象会导致它们的数学并集。
有关更多示例和解释,您可以参考此博客。
英文:
You can interpret every type as some mathematical set.
An intersection in the sets is a set of common elements
Union is the combination of all elements of two sets (A's elements that are not in B, the intersection of A and B, and B's elements that are not in A).
Any intersection with an empty set gives the empty set as a result. In the typescript empty set is never
. Thus, the following intersections will result in never
:
type Case1 = string & never // never
type Case2 = string[] & never // never
type Case3 = Record<string, unknown> & never // never
In the opposite union with never
will result in the other set:
type Case1 = string | never // string
type Case2 = string[] | never // string[]
type Case3 = Record<string, unknown> | never // Record<string, unknown>
Any string literal is a subset of string
, thus the intersection with string
would result in the string literal itself, but with the union, it would result in just a string:
type Case1 = "literal" | string // string
type Case2 = "literal" & string // "literal"
Since, objects keys have the type of PropertyKey
, which is string | number | symbol
, keyof Type
return this type, and if you need to extract only string keys you have two ways of doing it:
{[K in Extract<keyof Type, string>]: {}}
or
{[K in keyof Type & string]: {}}
Example of the second case:
type K = 'str' | 1 | number | '2';
type OnlyStringKeys = K & string // 'str' | '2'
Since there is no intersection between 1
, number
, and string
they will result in an empty set (never
).
In the context of intersecting two objects, it's a bit different and it is intentional behavior by the Typescript. The reason why it happens is that the compiler is unable to simplify the types, as it can do with primitive types, such as strings. Intersecting two objects result in their mathematical union.
For more example and explanations you can refer to this blog
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论