为什么这段代码在TypeScript 4.7之后编译失败?

xesrikrc  于 2023-01-10  发布在  TypeScript
关注(0)|答案(1)|浏览(137)

我有一个代码片段,以前是在TypeScript 4.6中编译的,但从TypeScript 4.7(4.7,4.8,4.9)开始编译失败。如果有问题的语句不是return语句,或者lambda函数只有一个参数,而不是两个参数,那么问题就解决了。
Playground链接

import { EventEmitter } from 'node:events';

// Simplified from discord.js https://github.com/discordjs/discord.js licence Apache 2.0
interface ClientEvents {
  warn: [message: string];
  shardDisconnect: [closeEvent: CloseEvent, shardId: number];
}

class BaseClient extends EventEmitter {
  public constructor() {
    super();
  };
}

type Awaitable<T> = PromiseLike<T> | T;

declare class Client extends BaseClient {
  public on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => Awaitable<void>): this;
  public on<S extends string | symbol>(
    event: Exclude<S, keyof ClientEvents>,
    listener: (...args: any[]) => Awaitable<void>,
  ): this;
}

// Demonstrative code
const bot = new Client();
// Return statement. Fails to compile, thinks that event is a union type of all first arguments (string | CloseEvent), but hovering over `event` shows just CloseEvent.
bot.on("shardDisconnect", (event, shard) => console.log(`Shard ${shard} disconnected (${event.code},${event.wasClean}): ${event.reason}`));
// Not a return statement. Compiles.
bot.on("shardDisconnect", (event, shard) => {
  console.log(`Shard ${shard} disconnected (${event.code},${event.wasClean}): ${event.reason}`);
});
// Return statement. Compiles.
bot.on("shardDisconnect", event => console.log(`${event.code} ${event.wasClean} ${event.reason}`))
eqqqjvef

eqqqjvef1#

看起来您可能遇到了与分布式条件类型相关的bug。ClientEvents[keyof ClientEvents]的类型是[message: string] | [closeEvent: CloseEvent, shardId: number]。虽然IDE似乎正确地认识到您的args应该是event, shardId,因为您已经将K区分为shardDisconnect,在某个时刻,解析器失去了该辨别,并且认为第一个参数是string | CloseEvent类型(这是来自warn和shardDisconnect的第一个参数的并集)。
看起来你可以通过使用条件类型作为参数类型来解决这个问题,这可以正确地重新缩小K:

public on<K extends keyof ClientEvents>(event: K, listener: (...args: K extends keyof ClientEvents ? ClientEvents[K] : any[]) => Awaitable<void>): this;

但是,如果你使用的是条件类型,你可以摆脱你的重载:

declare class Client extends BaseClient {
  public on<K extends string| symbol>(event: K, listener: (...args: K extends keyof ClientEvents ? ClientEvents[K] : any[]) => Awaitable<void>): this;  
}

我完全不知道为什么回调返回void或不改变行为。这听起来像是值得作为Typescript项目的一个问题打开的东西。
Playground链接

相关问题