这是不可能按要求直接实施的.
想象一下,例如,你要打电话给
const c = useCommand(
async (x: { a: string, b: number }) =>
console.log(x.a + " " + x.b.toFixed(2))
)
c("z", 1);
TypeScript types are erased upon compilation to JavaScript,所以上面的代码将如下所示:
const c = useCommand(
async (x) =>
console.log(x.a + " " + x.b.toFixed(2))
)
c("z", 1);
useCommand
的实现怎么知道"z"
和1
参数使用什么键呢?除了回调函数的body之外,代码中没有提到"a"
或"b"
.即使您可以以某种方式在JavaScript中查看函数体,看到x.a
和x.b
意味着输入类型至少需要是表单{a: ?, b: ?}
,也无法知道"z"
是在a
属性上还是在b
属性上,或者可能是在函数中碰巧没有提到的其他属性上.
您的Object.assign({}, obj)
实现当然不能做到这一点;它所做的只是将一个数组复制到一个对象中,因此您将["z", 1]
变成了{0: "z", 1: 1}
.这没用的...当您运行您的示例代码时,它的行为最终类似于{a: undefined, b: undefined}
.
最后,对象类型甚至在TypeScrip中都不维护可用的属性order.因此,即使字体{a: string, b: number}
没有被擦除,该字体在打字脚本中也与{b: number, a: string}
相同.它具有相同的属性,只是以不同的顺序编写.实际上,值{a: "z", b: 1}
和值{b: 1, a: "z"}
被TypeScrip同等对待,并且它们中的任何一个都可以分配给类型的任何一个版本.所以没有办法知道你是要打c("z", 1)
还是c(1, "z")
…这真的是无法解决的,特别是如果你有多个相同类型的参数,比如f(0, 1)
vsf(1, 0)
.
任何试图梳理对象类型的属性顺序的try 都会遇到各种奇怪的问题,比如在How to transform union type to tuple type中描述的那些问题.这不是一条你想走下go 的路.
唯一可行的方法是将一个键名数组传递给useCommand
,这样类型脚本编译器和JavaScript运行时都知道如何将这些键与参数相匹配.因此,呼叫可能如下所示:
const c = useCommand(
async (x: { a: string, b: number }) =>
console.log(x.a + " " + x.b.toFixed(2)),
["a", "b"]
)
如果您不介意,那么useCommand()
函数可能如下所示:
function useCommand<O extends
{ [P in K[number] | keyof O]: P extends K[number] ? any : undefined },
const K extends readonly (keyof O)[],
V = void
>(
fn: (arg: O) => Promise<V>,
keys: K,
onError: (e: unknown) => void = (e) => discard(e),
onSuccess: (ret: V) => void = (r) => discard(r)
) {
return async (...args: { [I in keyof K]: O[K[I]] }) => {
const requiredArgs = Object.fromEntries
(keys.map((k, i) => [k, args[i]])) as unknown as O
try {
if (!!arguments) {
const r = await fn(requiredArgs)
onSuccess(r)
}
} catch (err) {
onError(err)
}
}
}
在这里,我们添加了与keys
参数的tuple type对应的泛型类型K
(其中K
一直是modified with const
,以提示类型判断器您关心keys
参数的确切顺序和内容,因此它不会被推断为无用的string[]
类型).我们约束对象类型O
和类型K
,使它们彼此匹配;如果在K
中包含O
中不存在的键,或者反之亦然,则会出现错误.
然后,执行也必须改变. 输入args
的类型是mapped tuple type {[I in keyof K]: O[K[I]]}
,这意味着我们将K
中的每个键替换为O
中相应的元素类型.
现在,我们可以通过the Object.fromEntries()
method使用Object.fromEntries(keys.map((k, i) => [k, args[i]]))
而不是Object.assign({}, args)
. 对于keys
中的每个键和args
的相同位置i
处的参数,我们可以创建一个键为keys
、值为args[i]
的对象条目. 现在没有歧义和不确定性了;,第一个参数和keys
中的第一个键相对应,以此类推.
让我们试试看:
// const c: (args_0: string, args_1: number) => Promise<void>
c("z", 1); //"z 1.00"
看上go 不错!函数类型以正确的顺序具有正确的参数类型,并且实现也在运行时工作.
Playground link to code