reactjs - Vilken typdeklaration ska användas för en generisk T som kanske är hierarkisk eller inte?


Jag utökar en React-tabell (kreativt kallad "Table") som tar en generisk dataarray, tillsammans med viss kolumnkonfigurationsdata, och renderar en tabell. Deklarationen ser ut så här:

export class Table<T> extends React.Component<TableProps<T>, any>

De icke-valfria delarna av TableProps:

export interface TableProps<T> {
    columnConfigs: TableColumnConfig<T>;
    tableStore: TableStore<T>;
}

Och TableStore har bara en obligatorisk egenskap:

export class TableStore<T> {
    public data: T[];
}

TableColumnConfigs är inte särskilt viktigt, eftersom T endast används i en valfri egenskap:

export interface TableColumnConfigs<T> {
    render?: (data: T) => JSX.Element
}

Denna <Table> har redan använts på flera ställen, och jag lägger till en funktion till den: valfria hierarkiska data, så att vi kan sluta använda Kendo's TreeList (som är mycket, mycket funktionsrik, men lite långsam att ladda i vår miljö).

Mitt problem: Jag vill deklarera att T, datatypen som används i tabellen och som tillhandahålls av någon utanför klassen, kan ha en viss egenskap som gör att typen har rekursiva barn, men det är inte ett krav.

Jag har försökt detta:

export interface ChildData<T> {
    children?: (T & ChildData<T>)[]
}
export class Table<T extends ChildData<T>> extends React.Component<TableProps<T>, any> ...

Detta fungerar inte, eftersom den externa klassen inte definierar en egenskap som kallas "children".

Problemet är att TypeScript 2.8 inte gillar det: när jag faktiskt använder Table någonstans, gnäller det över att T inte implementerar ChildData. (Jag kompilerar med TS 2.3 just nu, men jag kommer att uppgradera förbi 2.4 vid något tillfälle, väldigt snart, jag lovar. Så småningom. Hur som helst, jag vill definitivt inte bli blockerad från att uppgradera. Jag vet att det inte fungerar på 2.8 eftersom min IDE kör Intellisense med 2.8, men jag kör 2.3-kompilatorn.)

Jag har också försökt deklarera med union och snittyper, men de kompilerar helt enkelt inte, inte ens under TS 2.3:

export class Table<T & ChildData<T>> extends React.Component<TableProps<T>, any> ...
export class Table<T | ChildData<T>> extends React.Component<TableProps<T>, any> ...

Finns det något sätt att säga "Jag har en generisk parameter T, som kan implementera en ChildData, men inte behöver göra det?" Min

-komponent används redan på flera andra ställen, och den kommer att användas av personer som inte är jag. Jag vill inte kräva att användare av Table också utökar sina T-typer med en "children"-egenskap som de inte använder (eftersom tabellens hierarkiska funktion är helt frivillig).

Svar #1

Efter lite pillande insåg jag till slut att ChildData bara spelar roll på ett ställe: inne i egenskapen "data" hos TableStore, vilket är där all relevant data finns. Så jag definierade om TableStore:

export class TableStore<T> {
    public data: (T & ChildData<T>)[];
    constructor(input: T[]) { this.data = input; }
}

Tydligen kan man inte använda & och | i generiska parametrar, men "extends" är okej. Och om man fortsätter att skicka runt generiska parametrar tappar TypeScript till slut bort de finare detaljerna av den generiska typen. Handlingen att ta T i klassen och använda (T & någotAnnat) i en egenskap tillåter mig att direkt tilldela en T till en (T & någotAnnat).

Jag antar att, på samma sätt som TypeScript 2.4 stramade upp generiska typer, kommer en framtida version av TypeScript att förbjuda de tricks jag för närvarande klarar mig med.

Observera att implementeringen är lite klumpig, eftersom jag måste caste till <T & ChildData<T>> på flera ställen inne i <Table>, samt i den konsumernade klassen när jag genererar TableStore:s data.

Relaterade artiklar


Alla rättigheter förbehållna. © Javascript-Typescript.com • 2025-2026