如何使用接口作为参数键入方法

How to type a method with an interface as parameter

我正在尝试定义一个方法签名,其中参数应符合接口。

interface Command {}

class HelloCommand implements Command {
  constructor(public readonly name: string) {}
}

type HandleCommandFn = (command: Command) => Promise<any>;

const handleHello: HandleCommandFn = (
  command: HelloCommand
): Promise<string> => {
  return Promise.resolve(`Hello ${command.name}`);
};

我从编译器中得到以下错误:

src/index.ts:9:7 - error TS2322: Type '(command: HelloCommand) => Promise<string>' is not assignable to type 'HandleCommandFn'.
  Types of parameters 'command' and 'command' are incompatible.
    Property 'name' is missing in type 'Command' but required in type 'HelloCommand'.

9 const handleHello: HandleCommandFn = (

我还尝试了以下变体:

type HandleCommandFn = <C extends Command>(command: C) => Promise<any>;
type HandleCommandFn = <C extends Command = Command>(command: C) => Promise<any>;

更新

借助下面的答案,我设法得到了以下代码(我的目标是定义一个类型安全的方法装饰器):

interface Command {
  getDescription(): string;
}

type HandleCommandFn<C extends Command = Command> = (
  command: C
) => Promise<any>;

function HandleCommand<C extends Command = Command>(
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<HandleCommandFn<C>>
): void {
  const method = descriptor.value;
  if (method) {
    // Do something
  }
}

class HelloCommand implements Command {
  constructor(public readonly name: string) {}

  public getDescription(): string {
    return "Say hello";
  }
}

class HelloCommandHandler {
  @HandleCommand // OK
  public execute(command: HelloCommand): Promise<string> {
    return Promise.resolve(`Hello ${command.name}`);
  }

  @HandleCommand // Error (wrong parameter type)
  public execute2(command: string): Promise<string> {
    return Promise.resolve(`Hello ${command}`);
  }

  @HandleCommand // Error (wrong return type)
  public execute3(command: HelloCommand): string {
    return `Hello ${command.name}`;
  }
}

这应该有效

interface Command {}

class HelloCommand implements Command {
    constructor(public readonly name: string) {}
}

type HandleCommandFn<C extends Command = Command> = (
    command: C
) => Promise<any>;

const handleHello: HandleCommandFn<HelloCommand> = (
    command
): Promise<string> => {
    return Promise.resolve(`Hello ${command.name}`);
};

这是因为函数参数是逆变的。 考虑这个例子:

interface Command { }

interface HelloCommand extends Command {
  name: string
}

declare var command: Command

declare var helloCommand: HelloCommand

command = helloCommand // ok , is assignable

直觉上,helloCommand 可以分配给 command 因为它更具体。而且很容易理解为什么子类型可以分配给超类型。

但是如果你看这个例子,事情就不再那么明显了:

let commandFn = (command: Command) => void 0

let helloCommandFn = (command: HelloCommand) => void 0

commandFn=helloCommandFn // error, is not assignable anymore

具有 HelloCommand 参数(Command 的子类型)的 FUnction 不再可分配给具有 Command(超类型)的函数。

这就是逆变的工作原理。继承的方向发生了变化是相反的方向。 你试图做的是不安全的,或者换句话说 - unsound 行为。

为了欺骗编译器,您可以添加额外的泛型:

type HandleCommandFn<T extends Command> = <U extends HelloCommand>(command: T & U) => Promise<any>;

// ok
const handleHello: HandleCommandFn<Command> = (
  command
): Promise<string> => {
  return Promise.resolve(`Hello ${command.name}`);
};