Ich habe eine Bibliothek, die einen Dienstprogramm-Typ ähnlich dem folgenden exportiert:
type Action<Model extends object> = (data: State<Model>) => State<Model>;
Mit diesem Dienstprogramm können Sie eine Funktion deklarieren, die als "Aktion" ausgeführt wird. Es erhält ein allgemeines Argument dafür, Model
dass die Aktion gegen sie vorgehen wird.
Das data
Argument der "Aktion" wird dann mit einem anderen Dienstprogrammtyp eingegeben, den ich exportiere.
type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;
Der State
Dienstprogrammtyp verwendet im Wesentlichen das eingehende Model
Generikum und erstellt dann einen neuen Typ, bei dem alle Eigenschaften vom Typ Action
entfernt wurden.
Zum Beispiel ist hier eine grundlegende Benutzerland-Implementierung des Obigen;
interface MyModel {
counter: number;
increment: Action<Model>;
}
const myModel = {
counter: 0,
increment: (data) => {
data.counter; // Exists and typed as `number`
data.increment; // Does not exist, as stripped off by State utility
return data;
}
}
Das obige funktioniert sehr gut. 👍
Es gibt jedoch einen Fall, mit dem ich zu kämpfen habe, insbesondere wenn eine generische Modelldefinition definiert ist, zusammen mit einer Factory-Funktion, um Instanzen des generischen Modells zu erzeugen.
Zum Beispiel;
interface MyModel<T> {
value: T; // 👈 a generic property
doSomething: Action<MyModel<T>>;
}
function modelFactory<T>(value: T): MyModel<T> {
return {
value,
doSomething: data => {
data.value; // Does not exist 😭
data.doSomething; // Does not exist 👍
return data;
}
};
}
Im obigen Beispiel erwarte ich, dass das data
Argument dort eingegeben wird, wo die doSomething
Aktion entfernt wurde und die generische value
Eigenschaft noch vorhanden ist. Dies ist jedoch nicht der Fall - die value
Eigenschaft wurde auch von unserem State
Dienstprogramm entfernt.
Ich glaube, die Ursache dafür ist, dass T
es generisch ist, ohne dass Typeinschränkungen / -einengungen angewendet werden, und daher entscheidet das Typsystem, dass es sich mit einem Action
Typ überschneidet, und entfernt ihn anschließend aus dem data
Argumenttyp.
Gibt es eine Möglichkeit, diese Einschränkung zu umgehen? Ich habe einige Nachforschungen angestellt und gehofft, dass es einen Mechanismus geben würde, in dem ich feststellen könnte, dass T
es einen außer einem gibt Action
. dh eine negative Typbeschränkung.
Vorstellen:
function modelFactory<T extends any except Action<any>>(value: T): UserDefinedModel<T> {
Diese Funktion ist für TypeScript jedoch nicht vorhanden.
Kennt jemand einen Weg, wie ich das so machen kann, wie ich es erwartet habe?
Um das Debuggen zu erleichtern, finden Sie hier ein vollständiges Code-Snippet:
// Returns the keys of an object that match the given type(s)
type KeysOfType<A extends object, B> = {
[K in keyof A]-?: A[K] extends B ? K : never
}[keyof A];
// Filters out an object, removing any key/values that are of Action<any> type
type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;
// My utility function.
type Action<Model extends object> = (data: State<Model>) => State<Model>;
interface MyModel<T> {
value: T; // 👈 a generic property
doSomething: Action<MyModel<T>>;
}
function modelFactory<T>(value: T): MyModel<T> {
return {
value,
doSomething: data => {
data.value; // Does not exist 😭
data.doSomething; // Does not exist 👍
return data;
}
};
}
Sie können mit diesem Codebeispiel hier spielen: https://codesandbox.io/s/reverent-star-m4sdb?fontsize=14