typescript - 如何将类型声明为联合类型的所有可能组合?
问题描述
TL,博士;
我在这里需要的是以某种方式声明给定联合的所有可能组合的联合。
type Combinations<SomeUnion, T extends any[]> = /* Some magic */
// ^^^^^^^^^^^^^^^
// this type argument provides the information
// about what is the length of expected combination.
// then
Combinations<string | number, ['x', 'y']> =
[string, string] |
[string, number] |
[number, string] |
[number, number]
Combinations<string | number | boolean, ['x', 'y']> =
[string, string] |
[string, number] |
[string, boolean] |
[number, string] |
[number, number] |
[number, boolean] |
[boolean, string] |
[boolean, number] |
[boolean, boolean]
Combinations<string | number, ['x', 'y', 'z']> =
[string, string, string] |
[string, string, number] |
[string, number, string] |
[string, number, number] |
[number, string, string] |
[number, string, number] |
[number, number, string] |
[number, number, number]
细节
我想定义一个方法装饰器,它可以安全地保证被装饰方法的参数数量与传递给该装饰器的参数数量完全相同。
type FixedLengthFunction<T extends any[]> = (...args: { [k in keyof T]: any }) => void
function myDecorator<T extends any[]>(...args: T) {
return <K extends string>(
target: { [k in K]: FixedLengthFunction<T> },
methodName: K,
desc: any
) => {}
}
// Note: WAI => Works as intented
class Foo {
@myDecorator()
a() {}
// expected to be correct,
// and actually passes the type system.
// WAI
@myDecorator()
b(x: number) {}
// expected to be incorrect since 'b' has one more argument,
// and actually catched by the type system.
// WAI
@myDecorator('m')
c(x: number) {}
// expected to be correct,
// and actually passes the type system.
// WAI
@myDecorator('m')
d() {}
// expected to be incorrect since 'd' has one less argument,
// but still passes the type system.
// not WAI
}
这同样适用于装饰方法的参数少于装饰器调用的所有场景。
根本原因是:
(a: SomeType) => void
兼容,(a: any, b: any) => void
因为any
可以未定义。
然后我修改FixedLengthFunction
成
type Defined = string | number | boolean | symbol | object
type FixedLengthFunction<T extends any[]> =
(...args: { [k in keyof T]: Defined }) => void
// ^^^^^^^
// changes: any -> Defined
但是,它变成“误报”并抱怨说
@myDecorator('m')
c(x: number) {}
是不正确的。
这次的原因是(x: number) => void
不兼容(arg_0: Defined) => void
。number
是缩小版本,Defined
缩小参数类型会破坏 LSP,因此会出现错误。
问题是:
FixedLengthFunction<['m', 'n']>
被解析为(...args: [Defined, Defined]) => void
被进一步解析为(arg_0: Defined, arg_1: Defined) => void
。
虽然我真正想要的是:
(...args:
[number, number] |
[string, number] |
[boolean, string]
/* ...and all other possible combinations of length 2 */
) => void
所以,我在这里需要的是Combinations
这篇文章顶部的神奇类型。
解决方案
产生这样的联合是一个坏主意。它会在编译过程中失控并产生性能问题。您可能可以使用递归类型别名来执行此操作,但非常不鼓励这样做(即,您可以欺骗编译器执行此操作,但将来可能无法使用)
话虽如此,我认为您发现的问题是错误的。您说参数较少的函数可以分配给参数较多的函数,因为any
. 它不是。一般来说,打字稿允许在期望具有更多参数的函数的地方分配具有较少参数的函数。函数实现将忽略额外的参数,不会有任何危害:
let fn: (a: number) => void = function () { console.log("Don't care about your args!"); }
fn(1)// 1 is ignored but that is ok
我们可以基于元组具有长度属性的事实以及我们可以推断类的实际类型并从类型中提取实际参数的事实来强制参数数量的严格相等:
type FixedLengthFunction<T extends any[]> = (...args: { [k in keyof T]: any }) => void
type ErrorIfDifferentLength<TMethod, TExpected extends any[]> =
TMethod extends (...a: infer TParams) => any ?
TParams['length'] extends TExpected['length'] ? {}: { "!Error": "Number of parameters differ:", actual: TParams['length'], expected: TExpected['length'] } : {}
function myDecorator<T extends any[]>(...a: T) {
return <K extends string, TClass extends Record<K, FixedLengthFunction<T>>>(target: TClass & ErrorIfDifferentLength<TClass[K], T>, key: K): void => {
}
}
// Note: WAI => Works as intented
class Foo {
@myDecorator()
a() {}
// expected to be correct,
// and actually passes the type system.
// WAI
@myDecorator()
b(x: number) {}
// expected to be incorrect since 'b' has one more argument,
// and actually catched by the type system.
// WAI
@myDecorator('m')
c(x: number) {}
// expected to be correct,
// and actually passes the type system.
// WAI
@myDecorator('m')
d() {}
// Argument of type 'Foo' is not assignable to parameter of type 'Foo & { "!Error": "Number of parameters differ:"; method: "d"; actual: 0; expected: 1; }'.
// WAI
}
推荐阅读
- java - Maven - 将属性写入文件
- c++ - qmake 等效于 cmake execute_process()
- sql - 为什么我的查询返回所有行的总和,而不是唯一匹配的行?
- php - 带数字和字母的代码序列,不带 0,1,o & i
- javascript - oidc-client.js 引发“未通过权限”错误
- html - 在 Internet Explorer 中使用 CSS 固定表头
- objective-c - 如何在目标 c-swift 桥接项目中使用 intentdefinition 文件?
- android - 添加行后更新 RecyclerView 列表,并将用户输入的值从该行设置到 recyclerview 列表
- python - 在python中将字符串值拆分为整数
- csv - 更正 Spring Cloud Dataflow Filesplitter Processor 中 csv 下一行的分隔符表达式