Node: 17.7.1

Typescript: 4.6.3

我在DDD上使用一个旧的回购协议,在try 重新创建代码时遇到了一个Typescript错误,我不知道如何修复.

使用以下代码注册时,AfterSomethingCreated类中出现IDE"错误":

Argument of type '(event: Event) => Promise<void>' is not assignable to parameter of type '(event: IEvent) => void'.
  Types of parameters 'event' and 'event' are incompatible.
    Property 'something' is missing in type 'IEvent' but required in type 'SomethingCreatedEvent'.ts(2345)

类SomethingCreatedEvent实现IEvent接口.SomethingCreatedEvent除了来自IEvent的属性之外,还包括一个属性.当包含属性时,抛出错误;当取出属性时,在IDE中抛出上述错误

代码:

IEvent.ts

export interface IEvent {
    //.....
}

IHandle.ts

export interface IHandle<IEvent> {
  setupSubscriptions(): void;
}

Events.ts

export class Events {
    
    //Methods...

    public static register(callback: (event: IEvent) => void, eventClassName: string): void {
        //Do Stuff
    }

    //Methods...

 }

SomethingCreatedEvent.ts

export class SomethingCreatedEvent implements IEvent {
  //.....
  public something: Something;

  constructor (something: Something) {
    this.something = Something;
    //.....
  }
  //......

  }
}

AfterSomethingCreated (Where Error Is Occurring)

export class AfterSomethingCreated implements IHandle<SomethingCreatedEvent> {
  constructor () {
    this.setupSubscriptions();
  }

  setupSubscriptions(): void {
    ---> ERROR -> Events.register(this.onSomethingCreatedEvent.bind(this), SomethingCreatedEvent.name);
  }
  private async onSomethingCreatedEvent (event: SomethingCreatedEvent): Promise<void> {
    //Do stuff
  }
}

推荐答案

发生错误的原因是Events.register()接受一个回调,该回调应该接受任何IEvent.因此,用尽可能少的IEvent调用回调应该是完全可以接受的(在您的例子中,因为IEvent是一个空接口,所以这只是{},空对象):

public static register(callback: (event: IEvent) => void, eventClassName: string): void {
    callback({}); // <-- look, no error
}

另一方面,onSomethingCreatedEvent()方法希望它的输入是SomethingCreatedEvent,所以这个方法访问SomethingCreatedEvent个对象特有的事件属性应该是完全可以接受的,比如something属性(我假设它的值是string,因为你没有在代码中定义Something类型.也就是说,我的行为就像type Something = string;):

private async onSomethingCreatedEvent(event: SomethingCreatedEvent): Promise<void> {
    console.log(event.something.toUpperCase());
}

但现在,在setupSubscriptions()中,您正在向Events.register()传递一个只接受SomethingCreatedEvent个事件的回调,这是一个错误:

setupSubscriptions(): void {
    Events.register(this.onSomethingCreatedEvent.bind(this), SomethingCreatedEvent.name); // error!
    // -----------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Argument of type '(event: SomethingCreatedEvent) => Promise<void>' is not
    // assignable to parameter of type '(event: IEvent) => void'.
}

这是一个有充分理由的错误.如果按上面修改的方式调用代码,则会出现运行时错误,因为我们调用的回调输入错误:

new AfterSomethingCreated();
// RUNTIME ERROR: Uncaught (in promise) TypeError: event.something is undefined

因为这是现有的代码,所以这在实践中可能不会发生.实际上,现有的一些JavaScript在技术上是不安全的.TypeScript以bivariant的方式判断method parameters,这意味着它将允许安全的缩小和不安全的扩大操作.Function个参数的判断更加严格(假设启用了the --strictFunctionTypes compiler option个参数,这是the --strict suite of compiler features的一部分).

如果想要获得更松散的类型行为,需要将callback的类型表示为method,而不是function.顺便说一下,区别在于:

interface Test {
    functionSyntax: (ev: IEvent) => void;
    methodSyntax(ev: IEvent): void;
}
const test: Test = {
    functionSyntax: (ev: SomethingCreatedEvent) => { }, // error!
    methodSyntax: (ev: SomethingCreatedEvent) => { } // okay!
}

看看TestfunctionSyntax的声明如何是一个带有箭头函数表达式类型的属性,而methodSyntax看起来更像是一个方法声明.并看看如何实施test个投诉关于functionSyntax个属性接受太窄的一种类型,而methodSyntax个属性不投诉.

因此,如果您只想 suppress 错误,可以依赖方法语法.这有点棘手,因为独立函数没有方法语法.你不能把(ev: IEvent): void写成一个类型,而{(ev: IEvent): void}被当作函数语法.这里的技巧是创建一个实际的方法类型,然后在周围的对象中创建index:

type MethodSyntax = { method(event: IEvent): void }["method"]
// type MethodSyntax = (event: IEvent) => void, but marked as a method

现在如果你用这个写Events.register():

public static register(callback: MethodSyntax, eventClassName: string): void { }

然后你的电话会突然正常工作:

Events.register(this.onSomethingCreatedEvent.bind(this), SomethingCreatedEvent.name); // okay

这不再是类型安全的,但至少它没有错误.


如果您关心强制执行类型安全,那么您可能需要重构,以便在处理程序处理回调时不会发生任何不好的事情.以下是一种可能的方法:

class Events {
    static handlers: ((event: IEvent) => void)[] = [];
    public static register<T extends IEvent>(
        callback: (event: T) => void,
        eventClass: new (...args: any) => T
    ): void {
        this.handlers.push(ev => ev instanceof eventClass && callback(ev));
    }
    public static handleEvent(event: IEvent) {
        this.handlers.forEach(h => h(event));
    }
}

现在Events.register()是一个generic函数,它接受一个只接受T类型事件的callback,以及一个eventClass构造函数(而不是类name).这样就可以 for each 事件调用每个处理程序...除非event instanceof eventClass,否则我们不会打callback(event).只有类name,编译器很难验证任何特定事件是否适合任何特定回调,因为类的name属性在TypeScript中不是强类型的(有关更多信息,请参阅microsoft/TypeScript#43325和内部链接的问题).

然后现在SomethingCreatedEvent美元接受以下条件:

    setupSubscriptions(): void {
        Events.register(this.onSomethingCreatedEvent.bind(this), SomethingCreatedEvent);
    }

虽然有些不合适的东西会被标记:

Events.register((o: SomethingCreatedEvent) => { }, Date) // error!
// Property 'something' is missing in type 'Date' but required in type 'SomethingCreatedEvent'.

Playground link to code

Node.js相关问答推荐

Mongoose:如何在文档中推送到Caped(有限大小,滚动窗口)数组?

FindOne()返回所有值,而不是一个值

mongodb首先自连接,然后根据某些条件与另一个表连接

Cypress.io - 如何等待返回调用属性的方法的结果?

未捕获的错误: 只能用作 元素的子元素,永远不会直接呈现.请将您的 包裹在

为什么后端开发需要单独的服务器?

在 nodejs 中使用 multer 上传文件返回未定义的 req.file 和空的 req.body

当我try 从本地主机发布新产品时收到错误消息

在 Atlas 触发器(Node JS)中正确初始化 Firebase 管理 SDK

在 NodeJS/ESP32 中通过 WebSocket 发送二进制数据 - 如何识别二进制和文本消息

无法通过谷歌Electron 表格 api( node js)中的服务帐户访问写入

NestJS TypeORM 可选查询不起作用

Ansible 将以什么用户身份运行我的命令?

为什么 JavaScript 的 parseInt(0.0000005) 打印5?

如何在 Mongoose 模式中设置数组大小限制

Sequelize 基于关联的查找

ChildProcess 关闭、退出事件之间的区别

使用 Webpack 和 font-face 加载字体

如何brew安装特定版本的Node?

如何从 Node.js 应用程序Ping?