J'ai une bibliothèque qui exporte un type d'utilitaire similaire au suivant:
type Action<Model extends object> = (data: State<Model>) => State<Model>;
Ce type d'utilitaire vous permet de déclarer une fonction qui fonctionnera comme une "action". Il reçoit un argument générique étant Model
l'action contre laquelle l'action va fonctionner.
L' data
argument de "l'action" est ensuite tapé avec un autre type d'utilitaire que j'exporte;
type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;
Le State
type d'utilitaire prend essentiellement le Model
générique entrant , puis crée un nouveau type où toutes les propriétés de type Action
ont été supprimées.
Par exemple, voici une mise en œuvre de base des terres utilisateur de ce qui précède;
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;
}
}
Ce qui précède fonctionne très bien. 👍
Cependant, il y a un cas avec lequel je me bats, en particulier lorsqu'une définition de modèle générique est définie, ainsi qu'une fonction d'usine pour produire des instances du modèle générique.
Par exemple;
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;
}
};
}
Dans l'exemple ci-dessus, je m'attends à ce que l' data
argument soit tapé là où l' doSomething
action a été supprimée et que la value
propriété générique existe toujours. Ce n'est cependant pas le cas - la value
propriété a également été retirée par notre State
service public.
Je crois que la cause de ceci est qu'il T
est générique sans aucune restriction de type / rétrécissement qui lui est appliqué, et donc le système de type décide qu'il intersecte avec un Action
type et le supprime ensuite du data
type d'argument.
Existe-t-il un moyen de contourner cette restriction? J'ai fait quelques recherches et j'espérais qu'il y aurait un mécanisme dans lequel je pourrais dire que T
c'est tout sauf un Action
. c'est-à-dire une restriction de type négatif.
Imaginer:
function modelFactory<T extends any except Action<any>>(value: T): UserDefinedModel<T> {
Mais cette fonctionnalité n'existe pas pour TypeScript.
Quelqu'un connaît-il un moyen de faire fonctionner cela comme je m'y attendais?
Pour faciliter le débogage, voici un extrait de code complet:
// 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;
}
};
}
Vous pouvez jouer avec cet exemple de code ici: https://codesandbox.io/s/reverent-star-m4sdb?fontsize=14