typescript - Array med vilken generisk typ som helst inuti


Jag vill deklarera typ som en array av par som innehåller en konstruktörsfunktion och ett argument som kan skickas till denna konstruktör. Och samma fråga om resterande funktionsargument. Hur kan jag göra det?

Till exempel i följande kod vill jag att rader med { type: A, config: "" } ska vara ett fel eftersom A inte accepterar string som en konstruktörsargument:

interface ISmth<T> {
  value: T;
}

interface ISmthConstructor<T> {
  new(value: T);
}

class A implements ISmth<number> {
  constructor(public value: number) {}
}

class B implements ISmth<string> {
  constructor(public value: string) {}
}

interface ISmthConfig<T> {
  type: ISmthConstructor<T>;
  config: T;
}; 

function f<T extends any[]>(...args: T) { // What should I use instead of `any`?
  return args;                            // It should array of ISmthConfig<U> with any `U`
}

var x = f(
  { type: A, config: 12 },
  { type: B, config: "" },
  { type: A, config: "" }, // I want this argument to be an error
)

var y: ISmthConfig<any>[] = [ // What should I use instead of `any`?
  { type: A, config: 12 },
  { type: B, config: "" },
  { type: A, config: "" }, // I want this line to be an error
];

Svar #1

Först: Jag kommer att ändra din definition av ISmthConstructor<T> till följande:

interface ISmthConstructor<T> {
  new(value: T): ISmth<T>; // added return type
}

Versionen i din kod returnerar en any-typ implicit, vilket är okej antar jag men verkar inte vara din avsikt.

Dagens version av TypeScript (3.1.0-dev.20180922) ger mig hopp om att det rätta svaret är att säga åt dig att vänta tills TypeScript 3.1 släpps någon gång den här månaden (sep 2018) och sedan göra så här:

declare function f<T extends any[]>(
  ...args: { [K in keyof T]: ISmthConfig<T[K]> }
): void;

// no error, T inferred as [number, string]
f(
  { type: A, config: 12 }, 
  { type: B, config: "" }
);

// error, T inferred as [number, string, string | number]
f(
  { type: A, config: 12 }, 
  { type: B, config: "" },
  { type: A, config: "" } // error on type: string is not assignable to number
);

Detta utnyttjar den kommande funktionen för mappade tuppler, samt inferens från mappade typer. I princip säger du att argumentet på position K måste vara en ISmthConfig<T[K]>. Om kompilatorn kan sluta sig till en tupel-/arraytyp T som uppfyller detta kriterium, lyckas det. Annars rapporterar det ett fel på den felaktiga parametern.

Hur som helst, det här är verkligen lovande! 😃

Om du behöver något som fungerar på TypeScript 3.0, är det bästa du kan göra att välja en maximal rimlig längd på argumentlistan och skapa en funktion som kan hantera det:

function goodEnough<A0, A1, A2, A3, A4, A5, A6>(
  ...args: [ISmthConfig<A0>?, ISmthConfig<A1>?, ISmthConfig<A2>?, ISmthConfig<A3>?,
    ISmthConfig<A4>?, ISmthConfig<A5>?, ISmthConfig<A6>?]
) {
  return args;
}
goodEnough({ type: A, config: 12 }); // okay
goodEnough({ type: B, config: "" }); // okay
goodEnough(
  { type: A, config: 12 },
  { type: B, config: "" },
  { type: A, config: "" } // error, string not assignable to number
); 

Detta har nackdelen att det har en godtycklig gräns och är ordrikt, men åtminstone fungerar det. 😐

Om den ovanstående signaturen visar sig ge dig en oanvändbar typ för args med många valfria tuple-element, eller om du behöver något som fungerar i TypeScript 2.9 och tidigare, då kan du göra en serie överlagringar, vilket är ännu mer omständligt:

function ugh<A0>(a0: ISmthConfig<A0>): [typeof a0];
function ugh<A0, A1>(
  a0: ISmthConfig<A0>, a1: ISmthConfig<A1>
): [typeof a0, typeof a1];
function ugh<A0, A1, A2>(
  a0: ISmthConfig<A0>, a1: ISmthConfig<A1>, a2: ISmthConfig<A2>
): [typeof a0, typeof a1, typeof a2];
function ugh(...args: ISmthConfig<any>[]): ISmthConfig<any>[] {
  return args;
}

ugh({ type: A, config: 12 }); // okay
ugh({ type: B, config: "" }); // okay
ugh(
  { type: A, config: 12 },
  { type: B, config: "" },
  { type: A, config: "" } // error, string not assignable to number
); 

Det är funktionellt, men, ja, usch. 🙁

Sammanfattningsvis: TypeScript 3.1 kommer att lösa detta på ett snyggt sätt; vänta på det (eller använd det nu) om du kan. Annars finns det reservlösningar/arbetsrundor med olika nivåer av avsmak.

Hoppas det hjälper. Lycka till!