TS类型交叉如何缩小类型范围?

huangapple go评论61阅读模式
英文:

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: '&paramOne=',
  ParamTwo: '&paramTwo=',
};

当然,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]: &amp;${Uncapitalize&lt;K&gt;}=;
};

const queryObject: Query<Params> = {
ParamOne: '&paramOne=',
ParamTwo: '&paramTwo=',
};

Of course TS won&#39;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 ? &amp;${Uncapitalize&lt;K&gt;}= : never;
};

It works fine, but recently I came across an alternative which I can&#39;t quite understand

type Query<T> = {
[K in keyof T]: &amp;${Uncapitalize&lt;K &amp; string&gt;}=;
};

I don&#39;t get how type intersection narrows `K` type in that case, I&#39;ve run some tests and it seems to work exactly like `extends` version.

**Question:**
How does it work? Shouldn&#39;t intersection only be used for combining several records properties? Is there an official explanation for this (I couldn&#39;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 | symbolkeyof Type 返回此类型,如果您需要提取只有字符串键的键,有两种方法可以做到:

{[K in Extract<keyof Type, string>]: {}}

{[K in keyof Type & string]: {}}

第二种情况的示例:

type K = 'str' | 1 | number | '2';
type OnlyStringKeys = K & string // 'str' | '2'

由于 1numberstring 之间没有交集,它们将导致空集合(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 &amp; never // never
type Case2 = string[] &amp; never // never
type Case3 = Record&lt;string, unknown&gt; &amp; 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&lt;string, unknown&gt; | never // Record&lt;string, unknown&gt;

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 = &quot;literal&quot; | string // string
type Case2 = &quot;literal&quot; &amp; string // &quot;literal&quot;

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&lt;keyof Type, string&gt;]: {}}

or

{[K in keyof Type &amp; string]: {}}

Example of the second case:

type K = &#39;str&#39; | 1 | number | &#39;2&#39;;
type OnlyStringKeys = K &amp; string // &#39;str&#39; | &#39;2&#39;

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

huangapple
  • 本文由 发表于 2023年6月16日 13:37:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/76487228.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定