typescript - Typvakt baserad på funktionsarity


Låt oss säga att jag har en funktion med flera valfria parametrar.

Varför finns det ingen typkontroll baserad på funktionens aritet genom arguments-nyckelordet och hur löser man detta utan att ändra implementationen och utan fula typomvandlingar?

function test(arg0?: string, arg1?: string, arg2?: string) {

    if (arguments.length === 1) {
        // I expect arg0 to be of type string instead of string | undefined
    }

    if (arguments.length === 2) {
        // I expect arg0 and arg1 to be of type string instead of string | undefined
    }

    if (arguments.length === 3) {
        // I expect arg0, arg1 and arg2 to be of type string instead of string | undefined
    }
}

Svar #1

Vänligen se arguments-typ:

interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}

interface IArguments {
    /** Iterator */
    [Symbol.iterator](): IterableIterator<any>;
}

Som du kanske har märkt, vet nyckelordet arguments ingenting om aritet för argumenttyp. Ur TypeScript-perspektiv är det inte ens bundet till funktionskontext. Det är bara ett objekt med en massa egenskaper som inte är relaterade till funktionsargument.

Dessutom beter sig length-egendomen inte som en typvakt. Du kan inte dra slutsatsen om till exempel [string, string]-typen bara genom att kontrollera om length === 2. Vänligen se detta svar. Du kommer att hitta lämpliga länkar till GitHub-ärenden och denna hasLengthAtLeast-hjälpare där.

Svar #2

Det för närvarande accepterade svaret är ganska bra och använder en typ-predikat för att begränsa typen.

Jag skulle föreslå ett alternativt sätt, genom att använda restparametrar, på följande sätt:

function test(...args: string[]) {
  if (args.length === 1) {
    console.log('arg0:', typeof args[0]);
  }

  if (args.length === 2) {
    console.log('arg0:', typeof args[0], 'arg1:', typeof args[1]);
  }

  if (args.length === 3) {
    console.log('arg0:', typeof args[0], 'arg1:', typeof args[1], 'arg2:', typeof args[2]);
  }
}

När det används med olika argument får vi följande resultat:

InmatningKompilerar?Utdata
test('one');jaarg0: sträng
test('one', 'two', 'three');jaarg0: sträng arg1: sträng arg2: sträng
test(1);nejfel TS2345: Argument av typen 'number' kan inte tilldelas parameter av typen 'string'.
test(undefined);nejfel TS2345: Argument av typen 'undefined' kan inte tilldelas parametern av typen 'string'.

En (möjligtvis stor) nackdel med detta tillvägagångssätt är att längden på args-arrayen inte är begränsad. Beroende på ditt användningsfall kan det vara tillräckligt att ignorera överflödiga argument, eller så kan du utföra en kontroll vid körning på följande sätt:

if (args.length > 3) {
  throw new Error(`Expected 3 arguments, but got ${args.length}`);
}

Tankeväckande.