javascript - Denna typparameter kan behöva en `extends`-begränsning i TypeScript


Tänk på dessa klasser och modell i TypeScript.

class ApplicantModel {
  name: string = '';
}

abstract class BaseApplicant {
  abstract handleApplicantData<T>(applicantData: T, isPrimaryApplicant: boolean): void;
}

class Applicant extends BaseApplicant {
  handleApplicantData<ApplicantModel>(applicantData: ApplicantModel): void {
    // Error: This type parameter might need an `extends ApplicantModel` constraint.
    this.processApplicant(applicantData);
  }

  processApplicant(applicant: ApplicantModel): void {
    console.log(applicant.name);
  }
}

Jag får ett fel:

Argument of type 'ApplicantModel' is not assignable to parameter of type 'ApplicantModel'.(2345)
input.tsx(10, 23): This type parameter might need an `extends globalThis.ApplicantModel` constraint.

Varför behöver jag använda extends-begränsning. Varför Argument av typen 'ApplicantModel' kan inte tilldelas parametern av typen 'ApplicantModel'?

Lekplats

Svar #1

Jag skulle säga att dina generiska typer har olämplig omfattning. Om BaseApplicant är en specifik klass med en generisk handleApplicantData-metod, så säger du att anropare av handleApplicantData kan ange vilken typargument för T de vill. Det betyder att någon skulle kunna anropa new Applicant().handleApplicantData(null) om de ville. Men du vill inte eller förväntar dig att BaseApplicant eller någon av dess underklasser ska vara så flexibel.

Om du vill att implementerare ska ange typargumentet, då vill du verkligen att BaseApplicant ska vara en generisk klass med en specifik handleApplicantData-metod. Det vill säga, flytta den generiska från handleApplicantData till BaseApplicant:

abstract class BaseApplicant<T> {
  abstract handleApplicantData(applicantData: T, isPrimaryApplicant: boolean): void;
}

Nu, istället för att låta din underklass försöka göra handleApplicantData generell på något sätt (vilket ledde till att du försökte och misslyckades med att specificera typargumentet som ApplicantModel, även om det bara var namnet på en ny generisk typparameter), kan dina underklasser välja vad T är:

class Applicant extends BaseApplicant<ApplicantModel> {
  handleApplicantData(applicantData: ApplicantModel): void {
    this.processApplicant(applicantData);
  }

  processApplicant(applicant: ApplicantModel): void {
    console.log(applicant.name);
  }
}

Detta kompilerar som önskat. En Applicant kan bara hantera ApplicantModel. Någon annan underklass av BaseApplicant skulle kunna hantera någon annan data.

Playground-länk till kod

Svar #2

I ditt fall här:

handleApplicantData<ApplicantModel>(applicantData: ApplicantModel): void {}

ApplicantModel avser namnet på generiken, inte typen, som
@Palladium02 nämner.

Det du vill göra är detta:

handleApplicantData<T extends ApplicantModel>(applicantData: T): void {}

Men eftersom det också ger ett fel måste du göra detta i BaseApplicant-klassen:

abstract handleApplicantData<T extends ApplicantModel>(
  applicantData: T,
  isPrimaryApplicant: boolean
): void;

Eller om du vill hålla det flexibelt:

abstract handleApplicantData(
  applicantData: unknown,
  isPrimaryApplicant: boolean
): void;

Detta skulle fortfarande tillåta dig att deklarera funktionen som generisk i underklassen.

Slutlig kod skulle vara:

class ApplicantModel {
  name: string = '';
}

abstract class BaseApplicant {
  abstract handleApplicantData(applicantData: unknown, isPrimaryApplicant: boolean): void;
}

class Applicant extends BaseApplicant {
  handleApplicantData<T extends ApplicantModel>(applicantData: T): void {
    this.processApplicant(applicantData);
  }

  processApplicant(applicant: ApplicantModel): void {
    console.log(applicant.name);
  }
}