我有一段这样的代码:

import { discard } from "../utilities/utility"

export type Arguments<T> = T[keyof T][]

export function useCommand<TObject extends {}, TValue = void>(
  fn: (arg: TObject) => Promise<TValue>,
  onError: (e: unknown) => void = (e) => discard(e),
  onSuccess: (ret: TValue) => void = (r) => discard(r)
) {
  return async (...obj: Arguments<TObject>) => {
    const arr: unknown = Object.assign({}, obj)
    const requiredArgs = arr as TObject
    try {
      if (!!arguments) {
        const r = await fn(requiredArgs)
        onSuccess(r)
      }
    } catch (err) {
      onError(err)
    }
  }
}

下面是我如何使用它

const func = useCommand(async (arg: {email: string, code: number}) =>{
   // some code 
})


// now elsewhere 
 func("JohnDoe", 2345)

 // this is wrong, one argument mising
func("JohnDoe")

// but this one is right, string|number
func(1, 2)

有没有可能有这样的乐趣:

 func(a: string, b: number)

try 了几种方法,但都不起作用,这在Typescript 中是否可行?

推荐答案

这是不可能按要求直接实施的.

想象一下,例如,你要打电话给

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.ax.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

Typescript相关问答推荐

Angular -使用Phone Directive将值粘贴到控件以格式化值时验证器不工作

TS 2339:属性切片不存在于类型DeliverableSignal产品[]上>'

Typescript对每个属性具有约束的通用类型

ReturnType此索引方法与点表示法之间的差异

TypeScript将键传递给新对象错误

将props传递给子组件并有条件地呈现它''

Redux—Toolkit查询仅在不为空的情况下添加参数

编剧错误:正在等待Expect(Locator).toBeVisible()

在排版修饰器中推断方法响应类型

在Mac和Windows上运行的Web应用程序出现这种对齐差异的原因是什么?(ReactNative)

有没有可能创建一种类型,强制在TypeScrip中返回`tyPeof`语句?

我可以使用typeof来强制执行某些字符串吗

NPM使用Vite Reaction应用程序运行预览有效,但我无法将其部署到Netlify

为什么在这种情况下,打字脚本泛型无法正确自动完成?

如何从上传文件中删除预览文件图标?

为什么TypeScrip假定{...省略类型,缺少类型,...选取类型,添加&>}为省略类型,缺少类型?

设置不允许相同类型的多个参数的线性规则

如何为带有参数的函数类型指定类型保护?

推断集合访问器类型

如何使用 Angular 确定给定值是否与应用程序中特定单选按钮的值匹配?