Ho una libreria che esporta un tipo di utilità simile al seguente:
type Action<Model extends object> = (data: State<Model>) => State<Model>;
Questo tipo di utilità consente di dichiarare una funzione che verrà eseguita come "azione". Riceve un argomento generico in base al Model
quale l'azione agirà.
L' data
argomento di "azione" viene quindi digitato con un altro tipo di utilità che esporto;
type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;
Il State
tipo di utilità prende sostanzialmente il Model
generico in entrata e quindi crea un nuovo tipo in cui tutte le proprietà di tipo Action
sono state rimosse.
Ad esempio, qui è un'implementazione di terra utente di base di quanto sopra;
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;
}
}
Quanto sopra funziona molto bene. 👍
Tuttavia, c'è un caso con cui sto lottando, in particolare quando viene definita una definizione di modello generico, insieme a una funzione di fabbrica per produrre istanze del modello generico.
Per esempio;
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;
}
};
}
Nell'esempio sopra mi aspetto che l' data
argomento venga digitato nel punto in cui l' doSomething
azione è stata rimossa e la value
proprietà generica esiste ancora. Questo comunque non è il caso: la value
proprietà è stata rimossa anche dalla nostra State
utility.
Credo che la causa T
sia generica senza che vi siano applicate restrizioni / restringimenti del tipo, e quindi il sistema dei tipi decide che si interseca con un Action
tipo e successivamente lo rimuove dal data
tipo di argomento.
C'è un modo per aggirare questa limitazione? Ho fatto qualche ricerca e spero che ci sarebbe un meccanismo in cui potrei affermare che T
è tutto tranne che per un Action
. cioè una restrizione di tipo negativa.
Immaginare:
function modelFactory<T extends any except Action<any>>(value: T): UserDefinedModel<T> {
Ma questa funzione non esiste per TypeScript.
Qualcuno sa come posso farlo funzionare come mi aspetto?
Per facilitare il debug ecco uno snippet di codice completo:
// 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;
}
};
}
Puoi giocare con questo esempio di codice qui: https://codesandbox.io/s/reverent-star-m4sdb?fontsize=14