从方法对象获取窄类型
Get Narrow Type from Object of Methods
我想要这样的缩小类型:
type Expected = {
method: 'firstNamespace/firstMethod'
payload: [string]
} | {
method: 'firstNamespace/secondMethod'
payload: [number, number]
} | {
method: 'secondNamespace/firstMethod'
payload: [number]
}
来自像这样的方法的对象:
const Methods = {
firstNamespace: {
firstMethod: (p: string) => {},
secondMethod: (p: number, k: number) => {}
},
secondNamespace: {
firstMethod: (p: number) => {}
}
}
我尝试了各种方法,但我认为我对 TypeScript 有一些误解。
我已经使用 TypeScript 将近一年了,任何超出基本类型的东西似乎都超出我的能力......
这可以在 TypeScript 中通过 recursive conditional types to drill down into the nested properties of Methods
, and template literal types 在类型级别将方法名称连接在一起。
让我们将您要查找的类型函数称为 MethodsToExpected<T>
,它采用对象类型 T
并生成相应方法参数类型的 union of object types whose method
property is a "/"
-delimited path to each method name and whose payload
property is a tuple。然后我们可以像这样根据自身递归地定义 MethodsToExpected<T>
:
type MethodsToExpected<T> = { [K in keyof T]-?:
T[K] extends (...args: infer P) => any ? { method: K, payload: P } :
MethodsToExpected<T[K]> extends infer X ? (
X extends { method: infer M, payload: infer P } ? (
{ method: `${Extract<K, string>}/${Extract<M, string>}`; payload: P }
) : never
) : never
}[keyof T]
构造 {[K in keyof T]-?: XXX}[keyof T]
,它立即 indexes into a mapped type 及其键,生成所有 XXX
类型的联合。
对于来自 T
的每个 属性 键 K
,我们检查 属性 类型 T[K]
。如果它是一个函数类型,那么我们获取它的参数列表列表并立即return {method: K, payload: P}
。否则我们将 MethodsToExpected<T[K]>
应用于 属性 并检查它。
(旁白:我正在使用 conditional type inference to "copy" MethodsToExpected<T[K]>
into a new type parameter X
, which I then use as the checked type in a distributive conditional type 以便 X
中的任何联合都分配给最终联合。如果您只是使用 MethodsToExpected<T[K]> extends {method: infer M, payload: infer P}
而不是中间 X
,它会产生类似 {method: "a/b" | "a/c", payload: [string] | [number]}
的东西,而不是所需的 {method: "a/b", payload: [string]} | {method: "a/c", payload: [number]}
。)
对于MethodsToExpected<T[K]>
的每个并集元素,我们取出method
类型M
和payload
类型P
,并构建一个新的method
/payload
对。 payload
类型没有改变,只是 P
,但我们在 M
前面加上当前键 K
和一个斜杠 "/"
类型。编译器不能确定 K
和 M
都是 string
类型所以它不想让你直接写 `${K}/${M}`
。相反,我们使用 the Extract<T, U>
utility type 来说服编译器我们只会连接 string
s.
让我们看看它是否有效:
type Expected = MethodsToExpected<typeof Methods>;
/* type Expected = {
method: "firstNamespace/firstMethod";
payload: [p: string];
} | {
method: "firstNamespace/secondMethod";
payload: [p: number, k: number];
} | {
method: "secondNamespace/firstMethod";
payload: [p: number];
} */
看起来不错。只是为了确保它深入到嵌套的子属性,让我们尝试一个不同的:
type Nested = MethodsToExpected<{ a: { b: { c: { d: { e: (f: string) => number } } } } }>;
/* type Nested = {
method: "a/b/c/d/e";
payload: [f: string];
} */
也不错
我想要这样的缩小类型:
type Expected = {
method: 'firstNamespace/firstMethod'
payload: [string]
} | {
method: 'firstNamespace/secondMethod'
payload: [number, number]
} | {
method: 'secondNamespace/firstMethod'
payload: [number]
}
来自像这样的方法的对象:
const Methods = {
firstNamespace: {
firstMethod: (p: string) => {},
secondMethod: (p: number, k: number) => {}
},
secondNamespace: {
firstMethod: (p: number) => {}
}
}
我尝试了各种方法,但我认为我对 TypeScript 有一些误解。
我已经使用 TypeScript 将近一年了,任何超出基本类型的东西似乎都超出我的能力......
这可以在 TypeScript 中通过 recursive conditional types to drill down into the nested properties of Methods
, and template literal types 在类型级别将方法名称连接在一起。
让我们将您要查找的类型函数称为 MethodsToExpected<T>
,它采用对象类型 T
并生成相应方法参数类型的 union of object types whose method
property is a "/"
-delimited path to each method name and whose payload
property is a tuple。然后我们可以像这样根据自身递归地定义 MethodsToExpected<T>
:
type MethodsToExpected<T> = { [K in keyof T]-?:
T[K] extends (...args: infer P) => any ? { method: K, payload: P } :
MethodsToExpected<T[K]> extends infer X ? (
X extends { method: infer M, payload: infer P } ? (
{ method: `${Extract<K, string>}/${Extract<M, string>}`; payload: P }
) : never
) : never
}[keyof T]
构造 {[K in keyof T]-?: XXX}[keyof T]
,它立即 indexes into a mapped type 及其键,生成所有 XXX
类型的联合。
对于来自 T
的每个 属性 键 K
,我们检查 属性 类型 T[K]
。如果它是一个函数类型,那么我们获取它的参数列表列表并立即return {method: K, payload: P}
。否则我们将 MethodsToExpected<T[K]>
应用于 属性 并检查它。
(旁白:我正在使用 conditional type inference to "copy" MethodsToExpected<T[K]>
into a new type parameter X
, which I then use as the checked type in a distributive conditional type 以便 X
中的任何联合都分配给最终联合。如果您只是使用 MethodsToExpected<T[K]> extends {method: infer M, payload: infer P}
而不是中间 X
,它会产生类似 {method: "a/b" | "a/c", payload: [string] | [number]}
的东西,而不是所需的 {method: "a/b", payload: [string]} | {method: "a/c", payload: [number]}
。)
对于MethodsToExpected<T[K]>
的每个并集元素,我们取出method
类型M
和payload
类型P
,并构建一个新的method
/payload
对。 payload
类型没有改变,只是 P
,但我们在 M
前面加上当前键 K
和一个斜杠 "/"
类型。编译器不能确定 K
和 M
都是 string
类型所以它不想让你直接写 `${K}/${M}`
。相反,我们使用 the Extract<T, U>
utility type 来说服编译器我们只会连接 string
s.
让我们看看它是否有效:
type Expected = MethodsToExpected<typeof Methods>;
/* type Expected = {
method: "firstNamespace/firstMethod";
payload: [p: string];
} | {
method: "firstNamespace/secondMethod";
payload: [p: number, k: number];
} | {
method: "secondNamespace/firstMethod";
payload: [p: number];
} */
看起来不错。只是为了确保它深入到嵌套的子属性,让我们尝试一个不同的:
type Nested = MethodsToExpected<{ a: { b: { c: { d: { e: (f: string) => number } } } } }>;
/* type Nested = {
method: "a/b/c/d/e";
payload: [f: string];
} */
也不错