Typoskript: Indexsignaturen im zugeordneten Typ
Wie kann ich den Typ { 'k': number, [s: string]: any }
und die Zusammenfassung übernehmen 'k'
und number
? Ich hätte gerne einen Typ-Alias T
, T<'k', number>
der den genannten Typ angibt.
Betrachten Sie das folgende Beispiel:
function f(x: { 'k': number, [s: string]: any }) {} // ok
type T_no_params = { 'k': number, [s: string]: any }; // ok
type T_key_only<k extends string> = { [a in k]: number }; // ok
type T_value_only<V> = { 'k': V, [s: string]: any}; // ok
type T_key_and_index<k extends string, V> = { [a in k]: V, [s: string]: any };// ?
- Die
{ 'k': number, [s: string]: any}
direkte Verwendung als Typ des Parameters der Funktionf
funktioniert. - Die Verwendung des
[s: string]: any
indizierten Teils intype
-alias funktioniert - Die Verwendung von
k extends string
intype
-alias funktioniert ebenfalls - Wenn ich das
k extends string
mit dem[s: string]: any
gleichentype
Alias kombiniere, erhalte ich einen Analysefehler (nicht einmal einen semantischen Fehler, es scheint nicht einmal eine gültige Syntax zu sein).
Das hier scheint zu funktionieren:
type HasKeyValue<K extends string, V> = { [s: string]: any } & { [S in K]: V }
Aber hier kann ich nicht ganz verstehen, warum es sich nicht über zusätzliche Eigenschaften beschwert (der Typ auf der rechten Seite &
sollte keine Objekte mit zusätzlichen Eigenschaften zulassen).
EDIT :
In den Antworten wurde mehrfach erwähnt, dass dies der &
Schnittpunktoperator ist, der sich ähnlich wie der satztheoretische Schnittpunkt verhalten soll. Dies ist jedoch nicht der Fall, wenn es um die Behandlung der zusätzlichen Eigenschaften geht, wie das folgende Beispiel zeigt:
function f(x: {a: number}){};
function g(y: {b: number}){};
function h(z: {a: number} & {b: number}){};
f({a: 42, b: 58}); // does not compile. {a: 42, b: 58} is not of type {a: number}
g({a: 42, b: 58}); // does not compile. {a: 42, b: 58} is not of type {b: number}
h({a: 42, b: 58}); // compiles!
In diesem Beispiel scheint es, als ob das {a: 42, b: 58}
weder vom Typ {a: number}
noch vom Typ ist {b: number}
, aber es endet irgendwie in der Kreuzung {a: number} & {b: number}
. So funktioniert die satztheoretische Schnittmenge nicht.
Das ist genau der Grund, warum mein eigener &
Vorschlag für mich so verdächtig aussah. Ich würde mich freuen, wenn jemand erläutern könnte, wie durch "Überschneiden" eines zugeordneten Typs { [s: string]: any }
der Typ "größer" werden kann, anstatt ihn kleiner zu machen.
Ich habe die Fragen gesehen
- Indexsignatur für einen zugeordneten Typ in Typescript
- Wie füge ich eine Indexsignatur für einen zugeordneten Typ hinzu?
aber diese schienen nicht direkt verwandt zu sein, obwohl sie einen ähnlichen Namen hatten.
Antworten
type HasKeyValue<K extends string, V> = { [s: string]: any } & { [S in K]: V }
ist der richtige Weg, um den Typ zu definieren, nach dem Sie suchen. Aber eine Sache zu wissen ist ( paraphrasieren veralteter Flagge: keyofStringsOnly ):
Der Operator keyof type gibt die Zeichenfolge | zurück Zahl anstelle von Zeichenfolge, wenn sie auf einen Typ mit einer Zeichenfolgenindexsignatur angewendet wird.
Ich kenne keine Methode, um den Index auf den string
Typ zu beschränken und nicht string | number
. Tatsächlich scheint es vernünftig zu sein, number
auf den string
Index zuzugreifen , da dies der Funktionsweise von Javascript entspricht (man kann immer eine Zahl stringifizieren). Andererseits können Sie nicht sicher auf einen Zahlenindex mit einem Zeichenfolgenwert zugreifen.
Der &
Typoperator arbeitet ähnlich, um theoretische Schnittpunkte festzulegen - er beschränkt immer die Menge möglicher Werte (oder lässt sie unverändert, erweitert sie jedoch nie). In Ihrem Fall schließt der Typ alle nicht stringähnlichen Schlüssel als Index aus. Um genau zu sein, schließen Sie unique symbol
als Index aus.
Ich denke, Ihre Verwirrung kann von der Art und Weise herrühren, wie Typescript Funktionsparameter behandelt. Das Aufrufen einer Funktion mit explizit definierten Parametern verhält sich anders als das Übergeben von Parametern als Variablen. In beiden Fällen stellt Typescript sicher, dass alle Parameter die richtige Struktur / Form haben, im letzteren Fall sind jedoch keine zusätzlichen Requisiten zulässig.
Code zur Veranschaulichung der Konzepte:
type HasKeyValue<K extends string, V> = { [s: string]: any } & { [S in K]: V };
type WithNumber = HasKeyValue<"n", number>;
const x: WithNumber = {
n: 1
};
type T = keyof typeof x; // string | number
x[0] = 2; // ok - number is a string-like index
const s = Symbol("s");
x[s] = "2"; // error: cannot access via symbol
interface N {
n: number;
}
function fn(p: N) {
return p.n;
}
const p1 = {
n: 1
};
const p2 = {
n: 2,
s: "2"
};
fn(p1); // ok - exact match
fn(p2); // ok - structural matching: { n: number } present; additional props ignored
fn({ n: 0, s: "s" }); // error: additional props not ignore when called explictily
fn({}); // error: n is missing
BEARBEITEN
Objektliterale - Das explizite Erstellen von Objekten mit einer bestimmten Form const p: { a: number} = { a: 42 }
wird von Typescript auf besondere Weise behandelt. Im Gegensatz zur regulären strukturellen Inferenz muss der Typ genau angepasst werden. Und um ehrlich zu sein, ist es sinnvoll, da diese zusätzlichen Eigenschaften - ohne zusätzlichen, möglicherweise unsicheren Guss - ohnehin nicht zugänglich sind.
[...] TypeScript vertritt jedoch die Auffassung, dass dieser Code wahrscheinlich einen Fehler enthält. Objektliterale werden speziell behandelt und auf ihre Eigenschaften überprüft, wenn sie anderen Variablen zugewiesen oder als Argumente übergeben werden. Wenn ein Objektliteral Eigenschaften hat, die der „Zieltyp“ nicht hat, wird eine Fehlermeldung angezeigt. [...] Eine letzte Möglichkeit, diese Überprüfungen zu umgehen, die etwas überraschend sein kann, besteht darin, das Objekt einer anderen Variablen zuzuweisen.
TS Handbuch
Die andere Möglichkeit, diesen Fehler zu umgehen, besteht darin, ... ihn mit zu überschneiden { [prop: string]: any }
.
Mehr Code:
function f(x: { a: number }) {}
function g(y: { b: number }) {}
function h(z: { a: number } & { b: number }) {}
f({ a: 42, b: 58 } as { a: number }); // compiles - cast possible, but `b` inaccessible anyway
g({ a: 42 } as { b: number }); // does not compile - incorrect cast; Conversion of type '{ a: number; }' to type '{ b: number; }' may be a mistake
h({ a: 42, b: 58 }); // compiles!
const p = {
a: 42,
b: 58
};
f(p); // compiles - regular structural typing
g(p); // compiles - regular structural typing
h(p); // compiles - regular structural typing
const i: { a: number } = { a: 42, b: 58 }; // error: not exact match
f(i); // compiles
g(i); // error
h(i); // error
Hier ist eine Argumentationsweise für den Kreuzungsoperator. Vielleicht hilft es:
type Intersection = { a: string } & { b: number }
Sie können Intersection
als "ein Objekt mit einer Eigenschaft a
vom Typ string
und einer Eigenschaft b
vom Typ number
" lesen . Das beschreibt auch diesen einfachen Typ:
type Simple = { a: string; b: number }
Und die beiden Typen sind kompatibel. Sie können für fast alle Zwecke eine durch die andere ersetzen.
Ich hoffe, dies erklärt, warum HasKeyValue
tatsächlich derselbe Typ ist, den Sie definieren wollten.
Warum T_key_and_index
nicht funktioniert, liegt daran, dass der erste Teil [a in k]: V
einen zugeordneten Typ definiert und Sie in der Definition eines zugeordneten Typs keine zusätzlichen Eigenschaften haben können. Wenn Sie einem zugeordneten Typ zusätzliche Eigenschaften hinzufügen müssen, können Sie einen Typschnittpunkt mit& erstellen .