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ć.
data
Argument „akcji” jest następnie wpisywane z innego typu użytkowego że eksport;
type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;
Typ State
narzędzia zasadniczo przyjmuje przychodzący Model
rodzajowy, a następnie tworzy nowy typ, w którym wszystkie właściwości tego typu Action
został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 data
argument zostanie wpisany w miejscu, w którym doSomething
akcja została usunięta, a value
właściwość ogólna nadal istnieje. Tak jednak nie jest - value
właściwość została również usunięta przez nasze State
narzędzie.
Uważam, że przyczyną tego jest to, że T
nie ma żadnych ograniczeń / zawężeń typu, a zatem system typów decyduje, że przecina się z Action
typem, a następnie usuwa go z data
typu 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 T
jest 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
źródło
Dokładnie tak, jak powiedziałeś, problem polega na tym, że nie mamy jeszcze negatywnych ograniczeń. Mam również nadzieję, że wkrótce udostępnią taką funkcję. Podczas oczekiwania proponuję takie obejście:
źródło
count
ivalue
zawsze sprawi, że kompilator będzie nieszczęśliwy. Aby to naprawić, możesz spróbować czegoś takiego:Ponieważ
Partial
używany jest typ narzędzia, wszystko będzie w porządku, jeślitransform
metoda nie jest dostępna.Stackblitz
źródło
Ogólnie czytam to dwa razy i nie do końca rozumiem, co chcesz osiągnąć. Z mojego rozumienia chcesz pominąć
transform
typ, który jest podany dokładnietransform
. Aby to osiągnąć, musimy użyć Pomiń :Nie jestem pewien, czy tego właśnie chciałeś ze względu na dość złożoną charakterystykę dodatkowych typów narzędzi. Mam nadzieję, że to pomoże.
źródło