为没有或许多不同参数类型的函数定义 Typescript 类型

Posted

技术标签:

【中文标题】为没有或许多不同参数类型的函数定义 Typescript 类型【英文标题】:Define a Typescript type for a function with none or many different parameter types 【发布时间】:2021-12-31 11:50:46 【问题描述】:

我有一个将传递的函数绑定到 htmlElement 的函数:

  global.d.ts:
  type tPrimitivRestParams = (number | object | string)[];
  type tDefaultVoidFunction = (...params: tPrimitivRestParams) => void;
  export default class AwesomeFunctions 
    public static addClickCallback(id: HTMLElement, func: tDefaultVoidFunction, scope: object, ...argArray: tPrimitivRestParams): void 
      [...] // checks
      // eslint-disable-next-line no-null/no-null
      element.onclick = func ? func.bind(scope, ...argArray) : null;
    
  
  export default class AwesomeClass 
    private bindMe(id: string):void 
    private bindMeToo():void 
    private bindMeThree(a:string, b: number, c: object): void 
    private bindMeFour(clazz: SomeClass): void 

    private binder():void 
      AwesomeFunctions.addClickCallback(document.getElementById('...'), this.bindMeToo, this); // TS says yes
      AwesomeFunctions.addClickCallback(document.getElementById('...'), this.bindMe, this, 'param'); // TS says no
      AwesomeFunctions.addClickCallback(document.getElementById('...'), this.bindMeThree, this, 'param', 2, whoopi: 'goldberg', age: 24, 'actual-age': 'dontcha dare askn!!!'); // TS says no
      AwesomeFunctions.addClickCallback(document.getElementById('...'), this.bindMeThree, this, new SomeClass()); // TS says no
    
  

它在 JS 上运行良好,但 TS 大喊大叫,但第一个除外:

// Example for bindMe
S2345: Argument of type '(id: string) => void' is not assignable to parameter of type 'tDefaultVoidFunction'.

如果我这样做:

AwesomeFunctions.addClickCallback(..., this.bindMe as tDefaultVoidFunction , ...);

TS 很满意,但我觉得不对。

我知道这是 tDefaultVoidFunction 类型的问题。是否可以定义一种类型以使 TS 对所有绑定感到满意?我不能使用任何、null 或 Function。

附加问题:

我也可以这样做:

AwesomeFunctions.addClickCallback(..., () => this.bindMe('param'), this);

TS 也很满意,但 95% 的 addClickCallback 函数变得毫无意义。但这不是我犹豫使用箭头函数的原因。我担心范围可能会有所不同。据我所知,范围始终是 this。所以在这种情况下,我声称箭头功能和绑定行为相同。我错了吗?


可重现的例子:

type tPrimitivRestParams = (number | object | string)[];
type tDefaultVoidFunction = (...params: tPrimitivRestParams) => void;

class AwesomeFunctions 
    public static addClickCallback(id: string, func: tDefaultVoidFunction, scope: object, ...argArray: tPrimitivRestParams): void 
        // DO something
    


class AwesomeClass 
    private bindMe(id: string):void 
    private bindMeToo():void 
    private bindMeThree(a:string, b: number, c: object): void 

    private binder():void 
        AwesomeFunctions.addClickCallback('A', this.bindMeToo, this);
        AwesomeFunctions.addClickCallback('B', this.bindMe, this, 'param');
        AwesomeFunctions.addClickCallback('C', this.bindMeThree, this, 'param', 2, whoopi: 'goldberg', age: 24, 'actual-age': 'dontcha dare askn!!!');
    

游乐场:TS Example

【问题讨论】:

请提供可重现的例子 @captain-yossarian 完成 【参考方案1】:

这就是你告诉编译器的:

type tDefaultVoidFunction = (...params: (number | object | string)[]) => void;

回调必须接受任意数量的参数,其中每个参数必须接受数字 |对象 |字符串。

让我们看看你的函数...

const example: tDefaultVoidFunction = (id: string): void 

参数只接受字符串,所以编译器会告诉你这是错误的。 (数字和对象丢失)

您可以使用联合类型定义多个允许的签名:

type tDefaultVoidFunction = ((id: string) => void) 
    | (() => void) 
    | ((a: string, b: number, c: object) => void) 
    | ((clazz: SomeClass) => void);

注意事项:

private function bindMe(id: string):void 在类中没有有效的 TypeScript 代码,我想这只是一个错字。 文档:https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html

基于 cmets 更新

可能的选择:

而不是写:

AwesomeFunctions.addClickCallback(document.getElementById('...'), this.bindMeToo, this);

使用:

document.getElementById('...')?.addEventListener('click', (e) => this.bindMeTo());
// ignoring that we do not provide valid parameters in both cases

【讨论】:

感谢您指出错字。很多 php 和 TS 之间的切换。 ;) 但我固执地解释了我的问题还不够。我知道TS抱怨的原因。但是我不能告诉 TS 我可能想通过的每一门课。那将是荒谬的。我有 40 多节课。这些函数也可以只接受一个数字、一个字符串或一个数组。你不能告诉我我必须告诉 TS 每种可能的类型。我会失去信心。在对象中,我们有类似 [key: string]: ... 我希望函数也有可能。 如果你想接受每个函数,答案是Function,它经常被阻止,因为它不可靠。当您调用回调时,您传递参数并且可用参数定义允许的签名。您将如何确保始终使用正确的参数调用您的回调?在您的代码中,您绑定到onclick,它将事件作为参数传递。这应该如何与需要字符串的函数一起使用?它不会得到字符串,而是PointerEvent,所以它会失败。 听起来合乎逻辑。但是我宁愿像在我的问题示例中那样使用包装函数。它只是一个没有参数的函数,它使用我想绑定的参数调用函数。问题是,我将不得不向一些开发人员介绍情况,我希望我可以避免这种情况。或者还可以。也许还有另一种解决方案。 类和箭头函数为处理this 带来了更多“类PHP/C#/Java”风格的javascript。例如。 “箭头函数根据定义箭头函数的范围建立‘this’。” (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…) - 在这个世界上可能没有理由bind()。例如。 (内部类方法)el.addEventListener('click', (e) => this.anotherClassMethod()) 可能是一个选项。 包装函数是目前工作中的首选。我们只需要更改 100 多个 LOC。 :) 测试用例现在运行没有任何抱怨。我仍然必须弄清楚,如果类型 tDefaultVoidFunction 现在已过时。无论如何...感谢您的帮助。我将在我们完成更改代码的那一天接受答案。将在本周末结束。

以上是关于为没有或许多不同参数类型的函数定义 Typescript 类型的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法使用“任意”变体作为参数?

Java构造函数

喵呜:C++基础系列:缺省函数函数重载

喵呜:C++基础系列:缺省函数函数重载

喵呜:C++基础系列:缺省函数函数重载

golang语言构造函数