2017-09-15 3 views
1

ich eine Column Schnittstelle haben, die Werte von Zeilen basierend auf den Tasten machen kann:Typoskript: Erzwingen konsistente Objektschlüssel als Typ-Parameter

interface Column<Row, Field extends keyof Row> { 
    key: Field; 
    render: (value: Row[Field]) => React.ReactNode; 
} 

Wenn ich explizit den Typ jeder Spalte, dann bekomme ich die gewünschte Verhalten. Zum Beispiel ist die folgende ungültig:

interface User { 
    id: number; 
    name: string; 
} 

const column: Column<User, "id"> = { 
    key: "id", 
    render(id: string) { 
     return id; 
    } 
}; 

da die id Eigenschaft ein String ist, keine Zahl. Gibt es eine Möglichkeit, dieses Verhalten zu erhalten, ohne den Typ jeder Spalte einzeln angeben zu müssen? Zum Beispiel werden die folgenden Typprüfungen:

const columns: Array<Column<User, keyof User>> = [ 
    { 
     key: "id", 
     render(id: string) { 
      return id; 
     } 
    } 
]; 

seit Field zu keyof User instanziiert wird, anstatt einem bestimmten Schlüssel.

+0

Wie wäre es Union Typ: ID: Nummer | Schnur; werfen Sie einen Blick: https://Stackoverflow.com/questions/38628115/what-does-the-pipe-mean-in-typescript – proti

+0

Ich bin mir nicht sicher, wie das hilft: Ich möchte, dass TypeScript nur 'id: Nummer als Argument. –

+0

ohh, OK. Ich habe deine Frage nicht verstanden. – proti

Antwort

1

Ah, ich liebe diese Art von Frage. Ein Problem, auf das Sie stoßen, ist, dass Sie mit TypeScript keine Typparameter und infer their types auslassen können. Sie möchten etwas wie Column<User> sagen, wobei der Typparameter aus dem Objektliteral abgeleitet wird, das Sie ihm übergeben. Nun, wir können das nicht direkt tun ... aber wir können diesen Effekt erhalten indirekt:

function columnsFor<Row>() { 
    function of<Field extends keyof Row>(column: Column<Row, Field>): Column<Row, Field> { 
    return column; 
    } 
    return of; 
} 

Die Funktion columnsFor ist sozusagen eine curried Funktion; Sie können den Typparameter explizit festlegen, und er gibt eine andere Funktion zurück, die Objekte von Column<Row,Field> für einen beliebigen gültigen Typ von Field akzeptiert. Hier ist, wie Sie es verwenden würde:

const userColumn = columnsFor<User>();  

const goodColumn = userColumn({ 
    key: "id", 
    render(id: number) { 
    return id; 
    } 
}); // ok, inferred as Column<User, "id"> 

const badColumn = userColumn({ 
    key: "id", 
    render(id: string) { 
    return id; 
    } 
}); // error as expected 

Die userColumn Funktion ist nur ein Argument einiger Column<User, Field> Art akzeptieren, und folgert Field von diesem Argument. Jetzt können Sie weitermachen und dieses Array machen:

const columns = [ 
    userColumn({ 
    key: "id", 
    render(id: number) { 
     return id; 
    } 
    }), // ok, inferred as Column<User,"id"> 
    userColumn({ 
    key: "name", 
    render(name: string) { 
     return name; 
    } 
    }), // ok, inferred as Column<User,"name"> 
    userColumn({ 
    key: "id", 
    render(id: string) { 
     return id; 
    } 
    }) // error as expected 
]; 

Hoffen Sie, dass das nützlich für Sie ist. Viel Glück!

+0

Danke, ich vermutete, dass so etwas möglich ist, aber ich war mir nicht sicher, was der idiomatische Ansatz in TypeScript wäre. Ich hatte gehofft, dass es eine Möglichkeit gibt, die korrekte Verwendung mit dem Typsystem zu garantieren, da es immer noch möglich ist, falschen Code zu erzeugen. Sie könnten dies mit diesem Ansatz tun, wenn TypeScript undurchsichtige Typen hätte, indem Sie 'Column' undurchsichtig machen, so dass alle Spalten mit' columnFor' erstellt werden müssen. –

+0

Sie könnten 'Column' eine Klasse mit einem privaten Konstruktor machen und dieser eine statische Methode geben, um nur die gewünschten' Column' Objekte zu erzeugen. Und der obige Code ist nicht genau falsch; Sie haben TypeScript gesagt, dass Sie ein Array von 'Column ' haben wollen, was Sie nicht wirklich meinen. – jcalz

+0

Danke, ich schaue mir private Konstrukteure an. Da der ursprüngliche Code nicht inkorrekt ist: Ich denke, die Spalte ist keine gültige Instanz von 'Column 'da die' render' Methode nicht beide Fälle behandelt. Ich denke das einzige Grund dafür ist, dass Typ-Checks wegen der (nicht zulässigen) bivarianten Funktionsparameter von TypeScript erfolgt. –