【问题标题】:How to type a method with an interface as parameter如何以接口为参数键入方法
【发布时间】:2021-11-09 09:26:17
【问题描述】:

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

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}`;
  }
}

【问题讨论】:

    标签: typescript typescript-typings typescript-generics


    【解决方案1】:

    这应该可行

    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}`);
    };
    
    

    【讨论】:

    • 我可以确认这是可行的。我的目标是定义一个类型安全的方法装饰器。我用完整的工作代码更新了问题。
    【解决方案2】:

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

    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 参数(命令的子类型)的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}`);
    };
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-26
    • 2018-07-11
    • 2021-02-20
    • 2015-10-03
    • 2019-08-25
    • 1970-01-01
    • 2017-03-05
    相关资源
    最近更新 更多