从 Typescript 中的类型中仅选择两个属性
Pick only two properties from a type in Typescript
我只需要从一个类型中选择两个名称尚未定义的属性,然后从那里创建一个新类型,其中一个属性是必需的,另一个属性是可选的。
我知道可以选择一个 属性 和
<T extends Record<string,any>> {
[K in keyof T]: (Record<K, T[K]> &
Partial<Record<Exclude<keyof T, K>, never>>) extends infer U ? { [P in keyof U]: U[P] } : never
}[keyof T]
但没有弄清楚如何(以及是否)可以使用此方法选择两个属性。
下面是我想如何使用它的示例
class Article {
name: string
id: number
content?: string
}
const article: TwoKeys<Article> = { id: 23 } // no error
const article: TwoKeys<Article> = { name: "my article", id: 122 } // no error
const article: TwoKeys<Article> = { name: "my article" , id: 23, content: "my content" } // error! we passed more than two props.
首先,让我们创建一个名为 PickOnly<T, K>
的辅助类型,其中您采用 object-like 类型 T
和一个键类型 K
(或 union of such keys) and produce a new object-like type where the properties of T
with keys in K
are known to be present (just like the Pick<T, K>
utility type) ,并且已知 T
中的键 not 不存在(Pick<T, K>
中不需要):
type PickOnly<T, K extends keyof T> =
Pick<T, K> & { [P in Exclude<keyof T, K>]?: never };
实施intersects Pick<T, K>
with a type that prohibits keys in T
other than those in K
. The type {[P in Exclude<keyof T, K>]?: never}
uses the Exclude<T, U>
utility type to get the non-K
keys of T
, and says that they must all be optional properties whose value type is the impossible never
type。可选的 属性 可能会丢失(或 undefined
取决于编译器选项),但 never
属性 不能存在......这意味着这些属性必须始终丢失(或 undefined
).
一个例子:
let x: PickOnly<{a: string, b: number, c: boolean}, "a" | "c">;
x = {a: "", c: true} // okay
x = {a: "", b: 123, c: true} // error!
// -------> ~
//Type 'number' is not assignable to type 'never'.
x = {a: ""}; // error! Property 'c' is missing
X
类型的值必须是 {a: number, c: boolean}
,而且根本不能包含 b
属性。
因此,您想要的 AtMostTwoKeys<T>
大概是 PickOnly<T, K>
的并集,每个 K
由 T
中的每组可能的键组成,最多两个元素。 Article
看起来像
| PickOnly<Article, never> // no keys
| PickOnly<Article, "name"> // only name
| PickOnly<Article, "id"> // only id
| PickOnly<Article, "content"> // only content
| PickOnly<Article, "name" | "id"> // name and id
| PickOnly<Article, "name" | "content"> // name and content
| PickOnly<Article, "id" | "content"> // id and content
那么让我们构建 AtMostTwoKeys<T>
。没有按键的部分很简单:
type AtMostTwoKeys<T> = (
PickOnly<T, never> |
)'
现在对于一个键...最简单的方法是通过 分布式对象类型 microsoft/TypeScript#47109. A type of the form {[K in KK]: F<K>}[KK]
, where you immediately index into a mapped type 中创造的形式产生 F<K>
KK
联盟中的所有 K
。
所以对于一个键,它看起来像:
type AtMostTwoKeys<T> = (
PickOnly<T, never> |
{ [K in keyof T]: PickOnly<T, K> }[keyof T]
);
哦,但是 in keyof T
使映射类型 , which will possibly introduce unwanted undefined
values in the output for optional input properties, I will preemptively use the -?
mapped type modifier 从映射中删除可选修饰符:
type AtMostTwoKeys<T> = (
PickOnly<T, never> |
{ [K in keyof T]-?: PickOnly<T, K> }[keyof T]
);
对于两个键,事情有点棘手。我们想在这里做两层分布式对象。第一个迭代 keyof T
中的每个键 K
,第二个应该引入一个新的类型参数(比如 L
)来做同样的事情。然后 K | L
将是来自 keyof T
的每对可能的键,以及每个键(当 K
和 L
相同时)。这 double-counts 个不同的对,但这不会伤害任何东西:
type AtMostTwoKeys<T> = (
PickOnly<T, never> |
{ [K in keyof T]-?: PickOnly<T, K> |
{ [L in keyof T]-?:
PickOnly<T, K | L> }[keyof T]
}[keyof T]
)
基本上就是这样,但是结果类型将以 PickOnly
:
的形式表示
type AMTKA = AtMostTwoKeys<Article>;
/* type AMTKA = PickOnly<Article, never> | PickOnly<Article, "name"> |
PickOnly<Article, "name" | "id"> | PickOnly<Article, "name" | "content"> |
PickOnly<Article, "id"> | PickOnly<Article, "id" | "content"> | \
PickOnly<Article, "content"> */
也许这很好。但通常我喜欢在它们的实际属性中引入一个的小帮手:
type AtMostTwoKeys<T> = (
PickOnly<T, never> |
{ [K in keyof T]-?: PickOnly<T, K> |
{ [L in keyof T]-?:
PickOnly<T, K | L> }[keyof T]
}[keyof T]
) extends infer O ? { [P in keyof O]: O[P] } : never
我们再试一次:
type AMTKA = AtMostTwoKeys<Article>;
/* type AMTKA =
| { name?: never; id?: never; content?: never; } // no keys
| { name: string; id?: never; content?: never; } // only name
| { name: string; id: number; content?: never; } // name and id
| { name: string; content?: string; id?: never; } // name and content
| { id: number; name?: never; content?: never; } // only id
| { id: number; content?: string; name?: never; } // id and content
| { content?: string; name?: never; id?: never; } // only content
*/
看起来不错!
为了确定,让我们检查一下您的示例用例:
let article: AtMostTwoKeys<Article>;
article = { id: 23 } // okay
article = { name: "my article", id: 122 } // okay
article = { name: "my article", id: 23, content: "my content" } // error!
成功!
我只需要从一个类型中选择两个名称尚未定义的属性,然后从那里创建一个新类型,其中一个属性是必需的,另一个属性是可选的。
我知道可以选择一个 属性 和
<T extends Record<string,any>> {
[K in keyof T]: (Record<K, T[K]> &
Partial<Record<Exclude<keyof T, K>, never>>) extends infer U ? { [P in keyof U]: U[P] } : never
}[keyof T]
但没有弄清楚如何(以及是否)可以使用此方法选择两个属性。
下面是我想如何使用它的示例
class Article {
name: string
id: number
content?: string
}
const article: TwoKeys<Article> = { id: 23 } // no error
const article: TwoKeys<Article> = { name: "my article", id: 122 } // no error
const article: TwoKeys<Article> = { name: "my article" , id: 23, content: "my content" } // error! we passed more than two props.
首先,让我们创建一个名为 PickOnly<T, K>
的辅助类型,其中您采用 object-like 类型 T
和一个键类型 K
(或 union of such keys) and produce a new object-like type where the properties of T
with keys in K
are known to be present (just like the Pick<T, K>
utility type) ,并且已知 T
中的键 not 不存在(Pick<T, K>
中不需要):
type PickOnly<T, K extends keyof T> =
Pick<T, K> & { [P in Exclude<keyof T, K>]?: never };
实施intersects Pick<T, K>
with a type that prohibits keys in T
other than those in K
. The type {[P in Exclude<keyof T, K>]?: never}
uses the Exclude<T, U>
utility type to get the non-K
keys of T
, and says that they must all be optional properties whose value type is the impossible never
type。可选的 属性 可能会丢失(或 undefined
取决于编译器选项),但 never
属性 不能存在......这意味着这些属性必须始终丢失(或 undefined
).
一个例子:
let x: PickOnly<{a: string, b: number, c: boolean}, "a" | "c">;
x = {a: "", c: true} // okay
x = {a: "", b: 123, c: true} // error!
// -------> ~
//Type 'number' is not assignable to type 'never'.
x = {a: ""}; // error! Property 'c' is missing
X
类型的值必须是 {a: number, c: boolean}
,而且根本不能包含 b
属性。
因此,您想要的 AtMostTwoKeys<T>
大概是 PickOnly<T, K>
的并集,每个 K
由 T
中的每组可能的键组成,最多两个元素。 Article
看起来像
| PickOnly<Article, never> // no keys
| PickOnly<Article, "name"> // only name
| PickOnly<Article, "id"> // only id
| PickOnly<Article, "content"> // only content
| PickOnly<Article, "name" | "id"> // name and id
| PickOnly<Article, "name" | "content"> // name and content
| PickOnly<Article, "id" | "content"> // id and content
那么让我们构建 AtMostTwoKeys<T>
。没有按键的部分很简单:
type AtMostTwoKeys<T> = (
PickOnly<T, never> |
)'
现在对于一个键...最简单的方法是通过 分布式对象类型 microsoft/TypeScript#47109. A type of the form {[K in KK]: F<K>}[KK]
, where you immediately index into a mapped type 中创造的形式产生 F<K>
KK
联盟中的所有 K
。
所以对于一个键,它看起来像:
type AtMostTwoKeys<T> = (
PickOnly<T, never> |
{ [K in keyof T]: PickOnly<T, K> }[keyof T]
);
哦,但是 in keyof T
使映射类型 undefined
values in the output for optional input properties, I will preemptively use the -?
mapped type modifier 从映射中删除可选修饰符:
type AtMostTwoKeys<T> = (
PickOnly<T, never> |
{ [K in keyof T]-?: PickOnly<T, K> }[keyof T]
);
对于两个键,事情有点棘手。我们想在这里做两层分布式对象。第一个迭代 keyof T
中的每个键 K
,第二个应该引入一个新的类型参数(比如 L
)来做同样的事情。然后 K | L
将是来自 keyof T
的每对可能的键,以及每个键(当 K
和 L
相同时)。这 double-counts 个不同的对,但这不会伤害任何东西:
type AtMostTwoKeys<T> = (
PickOnly<T, never> |
{ [K in keyof T]-?: PickOnly<T, K> |
{ [L in keyof T]-?:
PickOnly<T, K | L> }[keyof T]
}[keyof T]
)
基本上就是这样,但是结果类型将以 PickOnly
:
type AMTKA = AtMostTwoKeys<Article>;
/* type AMTKA = PickOnly<Article, never> | PickOnly<Article, "name"> |
PickOnly<Article, "name" | "id"> | PickOnly<Article, "name" | "content"> |
PickOnly<Article, "id"> | PickOnly<Article, "id" | "content"> | \
PickOnly<Article, "content"> */
也许这很好。但通常我喜欢在它们的实际属性中引入一个
type AtMostTwoKeys<T> = (
PickOnly<T, never> |
{ [K in keyof T]-?: PickOnly<T, K> |
{ [L in keyof T]-?:
PickOnly<T, K | L> }[keyof T]
}[keyof T]
) extends infer O ? { [P in keyof O]: O[P] } : never
我们再试一次:
type AMTKA = AtMostTwoKeys<Article>;
/* type AMTKA =
| { name?: never; id?: never; content?: never; } // no keys
| { name: string; id?: never; content?: never; } // only name
| { name: string; id: number; content?: never; } // name and id
| { name: string; content?: string; id?: never; } // name and content
| { id: number; name?: never; content?: never; } // only id
| { id: number; content?: string; name?: never; } // id and content
| { content?: string; name?: never; id?: never; } // only content
*/
看起来不错!
为了确定,让我们检查一下您的示例用例:
let article: AtMostTwoKeys<Article>;
article = { id: 23 } // okay
article = { name: "my article", id: 122 } // okay
article = { name: "my article", id: 23, content: "my content" } // error!
成功!