تایپاسکریپت - چگونه یک متد را با یک اینترفیس به عنوان پارامتر تایپ کنیم


من در حال تلاش برای تعریف یک امضای متد هستم که در آن یک پارامتر باید با یک اینترفیس مطابقت داشته باشد.

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) دیگر قابل انتساب به تابع با Command (فرانوع) نیست.

این نحوه کار کانتر وارینس است. جهت وراثت تغییر کرده و در جهت مخالف است. کاری که شما سعی در انجام آن دارید ناایمن است، یا به عبارت دیگر - رفتار ناصحیح.

برای گول زدن کامپایلر میتوانید یک جنریک اضافی اضافه کنید:

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