typescript - Cum să deriv proprietăți dintr-un parametru al unei clase generice?
Aș dori să implementez o clasă de tip mixin care aplică toate proprietățile unui argument de intrare asupra sa în constructor. Problema pe care o am este că nu pot găsi o modalitate de a tipiza corespunzător o astfel de clasă.
Ideal, mi-ar plăcea să ating toate cele 3 puncte:
- Funcționează — Este pur și simplu, din punct de vedere tehnic, fezabil.
- Direct — Transmiterea unui parametru de tip către
Foodar face ca acesta să moștenească toate proprietățile (împreună cu clasele (Steak) care extindFood). - Stabil — Nu se bazează pe comportamentul nedefinit al TypeScript.
class Thing<T extends Thing<any>> {
public declare thing: true;
constructor(public input: T) {}
}
class Meat extends Thing<Thing<any>> {
public declare meat: true;
}
class Beef extends Meat {
public declare beef: true;
}
class Food<T extends Meat> extends Thing<T> {
public declare food: true;
}
class Steak extends Food<Beef> {
do() {
this.thing; // ✔️, derives from `Food → Thing`
this.meat; // ❌, should derive from `Beef → Meat`
this.beef; // ❌, should derive from `Beef`
this.food; // ✔️, derives from `Food`
}
}
Am încercat:
Tipuri mapate direct în clasă
- Funcționează — Nu
- Direct — Nu
- Stabil — Nu
Acestea sunt cele două variante pe care le-am încercat mai întâi. Nicio șansă.
class Food<T extends Meat> extends Thing<T> {
[K in keyof T]: T[K]; // ']' expected. ts(1005)
}
class Food<T extends Meat> extends Thing<T> {
[K in T]: T[K]; // A mapped type may not declare properties or methods. ts(7061)
}
Aliașe de tip
- Funcționează — Da
Am venit cu acest tip de alias pentru fuziune. Funcționează, într-un fel, dar are mai multe avertismente:
- Trebuie aplicat clasei extinse (
Steak) în locul clasei de bază (Food) - Necesită parametrul generic al
Food(<T extends Meat>), altfel TypeScript va aruncaBase constructors must all have the same return type. ts(2510).
type Cooked<T extends Meat> = (new (
...args: ConstructorParameters<typeof Food>
) => Food<T> & T) &
typeof Food;
class Steak extends (Food as Cooked<Beef>) {
do() {
this.thing; // true
this.meat; // true
this.beef; // true
this.food; // true
}
}
Teren de joacă
Interfață generică care extinde ilegal
- Funcționează — Da
Acesta este cel mai interesant, deoarece pare să funcționeze 100%. Totuși, necesită o directivă // @ts-expect-error pentru a suprima erorile pe care le produce. Asta chiar nu mi se pare corect.
// without `// @ts-expect-error`:
// Interface 'Food<T>' cannot simultaneously extend types 'Thing<T>' and 'T'.
// Named property 'input' of types 'Thing<T>' and 'T' are not identical. ts(2320)
// @ts-expect-error
interface Food<T extends Meat> extends T {}
class Food<T extends Meat> extends Thing<T> {
public declare food: true;
}
class Steak extends Food<Beef> {
do() {
this.thing; // true
this.meat; // true
this.beef; // true
this.food; // true
}
}
Încercarea de a folosi Omit pentru a elimina proprietățile care se suprapun împiedică funcționarea acestuia complet:
// An interface can only extend an object type or intersection of object types with statically known members. ts(2312)
interface Food<T extends Meat> extends Omit<T, keyof Thing> {}
Teren de joacă