Mam bibliotekę, która eksportuje typ narzędzia podobny do następującego:
type Action<Model extends object> = (data: State<Model>) => State<Model>;
Ten typ narzędzia pozwala zadeklarować funkcję, która będzie działać jako „akcja”. Otrzymuje ogólny argument, Modelże akcja będzie działać.
dataArgument „akcji” jest następnie wpisywane z innego typu użytkowego że eksport;
type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;
Typ Statenarzędzia zasadniczo przyjmuje przychodzący Modelrodzajowy, a następnie tworzy nowy typ, w którym wszystkie właściwości tego typu Actionzostały usunięte.
Na przykład tutaj jest podstawowa implementacja powyższego gruntu użytkownika;
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;
}
}
Powyższe działa bardzo dobrze. 👍
Jest jednak przypadek, z którym walczę, szczególnie gdy zdefiniowana jest ogólna definicja modelu, wraz z funkcją fabryczną do tworzenia instancji modelu ogólnego.
Na przykład;
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;
}
};
}
W powyższym przykładzie oczekuję, że dataargument zostanie wpisany w miejscu, w którym doSomethingakcja została usunięta, a valuewłaściwość ogólna nadal istnieje. Tak jednak nie jest - valuewłaściwość została również usunięta przez nasze Statenarzędzie.
Uważam, że przyczyną tego jest to, że Tnie ma żadnych ograniczeń / zawężeń typu, a zatem system typów decyduje, że przecina się z Actiontypem, a następnie usuwa go z datatypu argumentu.
Czy istnieje sposób na obejście tego ograniczenia? Zrobiłem rozeznanie i miał nadzieję, że będzie jakiś mechanizm, w którym mogę stwierdzić, że Tjest dowolny z wyjątkiem dla Action. tj. ograniczenie typu ujemnego.
Wyobrażać sobie:
function modelFactory<T extends any except Action<any>>(value: T): UserDefinedModel<T> {
Ale ta funkcja nie istnieje dla TypeScript.
Czy ktoś wie, jak mogę sprawić, by działało tak, jak się tego spodziewam?
Aby ułatwić debugowanie, tutaj jest pełny fragment kodu:
// 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;
}
};
}
Możesz grać z tym przykładem kodu tutaj: https://codesandbox.io/s/reverent-star-m4sdb?fontsize=14