Machen Sie einen Funktionsparameter in TypeScript erforderlich

Sep 09 2020

Im folgenden Code:

type NoArg = {
  (): void
}

type OneArg = {
  (x:number): void
}

let noArg: NoArg = (x:number)=>{}
let oneArg: OneArg = ()=>{}

Nur der zuerst zugewiesene generiert einen Compilerfehler. Ich verstehe, warum dies der Fall ist, weil JavaScript die Übergabe von Funktionen mit weniger als dem vollständigen Satz möglicher Argumente ermöglicht. Dies unterscheidet sich von der Aussage, dass die Argumente optional sind, was eher mit dem Aufruf der Funktion als mit der Übergabe zu tun hat . Siehe die FAQ .

Gibt es jedoch eine Möglichkeit, eine Version der OneArg-Schnittstelle zu erstellen, die nicht mit einer Nullargumentfunktion kompatibel ist?

Ich verstehe, dass dies durch Branding oder nominelle Eingabe erfolgen kann, z.

type OneArg = {
  (x:number): void
  _brand: “OneArg”
}

Für diese oder eine andere Art von nominaler Typisierungslösung ist jedoch zusätzliche Arbeit an der Zuweisung erforderlich (z. B. müssen Sie der Funktion die Eigenschaft _brand explizit hinzufügen).

Meine Frage lautet also: Gibt es eine Möglichkeit, einen Typ NoArg zu erstellen, bei dem die einfache Zuweisung fehlschlägt let oneArg: OneArg = ()=>{}?

In den oben verlinkten FAQ heißt es: „In TypeScript gibt es derzeit keine Möglichkeit, anzuzeigen, dass ein Rückrufparameter vorhanden sein muss.“ Schließt dies vollständig aus, was ich hier tun möchte? Ich hoffe, dass dies nicht der Fall ist, da dies kein Rückrufparameter ist, aber vielleicht ist die Begründung dieselbe.

UPDATE: In den Kommentaren unten wurde die Frage aufgeworfen, ob dies mit einem Typschutztest erreicht werden kann. Die Antwort lautet, soweit ich das beurteilen kann, nein, da die Typüberlappung bedeutet, dass der Typschutz die Typen nicht einschränkt. Sie können es auf diesem Spielplatz sehen .

Antworten

sam256 Sep 10 2020 at 00:14

Nach einigen Diskussionen mit @ aluan-haddad habe ich eine Teillösung. Ich kann nicht genau das bekommen, wonach ich gefragt habe, dh eine "automatische" Typdefinition, die zwischen einer Funktion mit dem erforderlichen Parameter und einer ohne unterscheidet, aber ich habe eine "Diskriminator" -Funktion entwickelt, die zwischen den beiden Funktionen unterscheidet und wenden Sie einen geeigneten Markentyp an.

Die Lösung basiert auf der Verwendung eines bedingten Typs in Verbindung mit der Tatsache, dass die Ein-Argument-Funktion nicht mit dem Null-Argument-Typ kompatibel ist (obwohl das Gegenteil nicht der Fall ist), um die richtigen Typen zu erzwingen.

type NoArg = {
    _brand: 'NoArg'
    ():void
}

type OneArg = {
  _brand: 'OneArg'
  (x:number): void
}

// Correct typings
let noArg: NoArg = discriminator(()=>{})
let oneArg: OneArg = discriminator((x:number)=>{})

// Both of these error, as hoped!
let noArgError: NoArg = discriminator((x:number)=>{})
let oneArgError: OneArg = discriminator(()=>{})

function discriminator<T extends (x:any)=>any>(myFunc: T) {
    let discriminatedFunc
    if(myFunc.length === 0) {
        discriminatedFunc = {
            _brand: 'NoArg',
            myFunc
        }
    } 
    else {
        discriminatedFunc = {
            _brand: 'OneArg',
            myFunc
        }
    }
    return discriminatedFunc as unknown as T extends ()=>any ? NoArg : OneArg
}

Und auf einem Spielplatz .

NB: Meines T extends (x:number)=>any ? OneArg : NoArgErachtens hängt dies davon ab , dass die Funktion mit einem Argument nicht mit der Funktion vom Typ Null kompatibel ist. Beachten Sie, dass die umgekehrte Version des bedingten Typs nicht funktioniert, da sie immer in OneArg aufgelöst wird.

Ist dies nun besser, als die Funktionen zunächst über Konstruktorfunktionen zu "brandmarken"? Ich denke schon, weil Sie nur eine Diskriminatorfunktion gegenüber zwei Markenfunktionen benötigen. Und auch, weil Sie die Signaturen Ihrer Funktionen nicht unbedingt ex ante kennen müssen, dh wenn Sie sie an den Diskriminator übergeben.

Hat jemand Probleme mit dieser Lösung? Gibt es eine bessere oder ist das alles TS erlaubt?