typescript - 如何在 TypeScript 中定义泛型数组?

标签 typescript generics

假设我有一个如下所示的通用接口(interface):

interface Transform<ArgType> {
    transformer: (input: string, arg: ArgType) => string;
    arg: ArgType;
}

然后我想应用这些 Transform 的数组到 string .如何定义这个 Transform 数组这样它就可以验证 <ArgType>Transform.transformer 中是等价的和 Transform.arg ?我想写这样的东西:

function append(input: string, arg: string): string {
    return input.concat(arg);
}

function repeat(input: string, arg: number): string {
    return input.repeat(arg);
}

const transforms = [
    {
        transformer: append,
        arg: " END"
    },
    {
        transformer: repeat,
        arg: 4
    },
];

function applyTransforms(input: string, transforms: \*what type goes here?*\): string {
    for (const transform of transforms) {
        input = transform.transformer(input, transform.arg);
    }

    return input;
}

在这个例子中,我定义了什么类型const transforms为了让类型系统验证数组中的每个项目都满足通用 Transform<ArgType>界面?

最佳答案

(以下使用TS 3.0)

如果 TypeScript 直接支持 existential types ,我会告诉你使用它们。存在类型意味着“我只知道该类型存在,但我不知道也不关心它是什么”。那么你的transforms参数的类型类似于 Array< exists A. Transform<A> > ,意思是“对于一些 Transform<A> 来说是A 的数组”。有一个suggestion在语言中允许这些类型,但很少有语言支持这一点,所以谁知道呢。

你可以“放弃”,只使用 Array<Transform<any>> ,这会起作用,但无法捕获像这样的不一致情况:

applyTransforms("hey", [{transformer: repeat, arg: "oops"}]); // no error

但正如您所说,即使在没有存在类型的情况下,您也在寻求强制一致性。幸运的是,有不同复杂程度的变通方法。这是一个:


让我们声明一个接受 T 的类型函数, 如果它是 Transform<A>对于一些 A , 它返回 unknown (新的 top type 匹配每个值...所以 unknown & T 等于 T 对于所有 T ),否则返回 never (bottom type 不匹配任何值...所以 never & T 等于 never 对于所有 T ):

type VerifyTransform<T> = unknown extends
  (T extends { transformer: (input: string, arg: infer A) => string } ?
    T extends { arg: A } ? never : unknown : unknown
  ) ? never : unknown

它使用 conditional types计算那个。这个想法是它看 transformer弄清楚A , 然后确保 argA 兼容.

现在我们可以输入 applyTransforms作为只接受 transforms 的通用函数匹配元素类型为 T 的数组的参数匹配VerifyTransform<T> :

function applyTransforms<T extends Transform<any>>(
  input: string, 
  transforms: Array<T> & VerifyTransform<T>
): string {
  for (const transform of transforms) {
    input = transform.transformer(input, transform.arg);
  }
  return input;
}

在这里我们看到它在工作:

applyTransforms("hey", transforms); // okay

如果你传入不一致的东西,你会得到一个错误:

applyTransforms("hey", [{transformer: repeat, arg: "oops"}]); // error

错误不是特别明显:“[ts] Argument of type '{ transformer: (input: string, arg: number) => string; arg: string; }[]' is not assignable to parameter of type 'never'.”但至少它是一个错误。


或者,您可能会意识到,如果您所做的只是传递 argtransformer ,你可以让你的存在式SomeTransform像这样输入:

interface SomeTransform {
  transformerWithArg: (input: string) => string;
}

并制作一个SomeTransform来自任何 Transform<A>你想要:

const makeSome = <A>(transform: Transform<A>): SomeTransform => ({
  transformerWithArg: (input: string) => transform.transformer(input, transform.arg)
});

然后接受一个 SomeTransform 的数组相反:

function applySomeTransforms(input: string, transforms: SomeTransform[]): string {
  for (const someTransform of transforms) {
    input = someTransform.transformerWithArg(input);
  }

  return input;
}

看看它是否有效:

const someTransforms = [
  makeSome({
    transformer: append,
    arg: " END"
  }),
  makeSome({
    transformer: repeat,
    arg: 4
  }),
];

applySomeTransforms("h", someTransforms);

如果您尝试不一致地执行此操作:

makeSome({transformer: repeat, arg: "oops"}); // error

你得到一个更合理的错误:“Types of parameters 'arg' and 'arg' are incompatible. Type 'string' is not assignable to type 'number'.


好的,希望对你有帮助。祝你好运。

关于typescript - 如何在 TypeScript 中定义泛型数组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51879601/

相关文章:

javascript - Typescript Web 音频 API 缺少定义

typescript - 为什么 DeepWriteable 的品牌字符串/数字类型不再扩展字符串/数字?

typescript - 如何编写依赖于另一个定义文件的 TypeScript 定义文件?

c# - 将非通用集合转换为通用集合的最佳方法是什么?

java - 如何将 'cast' 转换为通用对象并使用通用参数执行方法

c# - 查找并调用通用重载方法

swift - 协议(protocol)不符合自身?

typescript - 创建和使用 TypeScript 库的故事是什么?

javascript - 如何使用angular7关闭api响应的ngx-dialog?

java - 扩展或等于的通用语法