- Bonjour à tous, je m'appelle Alexander Nikolaichev. Je travaille chez Yandex.Cloud en tant que développeur front-end, travaillant sur l'infrastructure interne de Yandex. Aujourd'hui, je vais vous parler d'une chose très utile, sans laquelle il est difficile d'imaginer une application moderne, surtout à grande échelle. C'est TypeScript, le typage, un sujet plus étroit - les génériques, et pourquoi ils sont nécessaires.
Tout d'abord, répondons à la question de savoir pourquoi TypeScript et qu'est-ce que l'infrastructure a à voir avec cela. Notre principale propriété de l'infrastructure est sa fiabilité. Comment cela peut-il être assuré? Tout d'abord, vous pouvez tester.
Nous avons des tests unitaires et d'intégration. Les tests sont une bonne pratique standard.
Vous devez également utiliser la révision du code. En outre - collection d'erreurs. Si, néanmoins, une erreur se produit, un mécanisme spécial l'envoie et nous pouvons rapidement réparer quelque chose.
Comme ce serait bien de ne pas faire d'erreur du tout. Pour cela, il y a la saisie, ce qui ne nous permettra pas du tout d'obtenir une erreur à l'exécution. Yandex utilise la norme industrielle TypeScript. Et comme les applications sont volumineuses et complexes, nous obtiendrons cette formule: si nous avons une interface, un typage et même des abstractions complexes, nous en viendrons certainement aux génériques TypeScript. Vous ne pouvez pas vous en passer.
Syntaxe
Pour mener un programme éducatif de base, examinons d'abord les bases de la syntaxe.
Un générique dans TypeScript est un type qui dépend d'un autre type.
Nous avons un type simple, Page. Nous le paramétrons avec un certain paramètre <T>, il est écrit entre crochets angulaires. Et nous voyons qu'il y a des chaînes, des nombres, mais <T> est variable.
En plus des interfaces et des types, nous pouvons appliquer la même syntaxe aux fonctions. Autrement dit, le même paramètre <T> est transmis à l'argument de la fonction, et dans la réponse, nous réutiliserons la même interface, nous la transmettrons également à cet endroit.
Notre appel générique est également écrit entre crochets avec le type souhaité, comme lors de son initialisation.
Une syntaxe similaire existe pour les classes. Nous lançons le paramètre dans des champs privés, et nous avons une sorte de getter. Mais nous n'écrivons pas le type ici. Pourquoi? Parce que TypeScript peut déduire le type. C'est une de ses fonctionnalités très utile, et nous l'appliquerons.
Voyons ce qui se passe lors de l'utilisation de cette classe. Nous créons une instance, et au lieu de notre paramètre <T>, nous passons l'un des éléments d'énumération. Nous créons une énumération - russe, anglais. TypeScript comprend que nous avons passé un élément de l'énumération et en déduit le type lang.
Mais voyons comment fonctionne l'inférence de type. Si au lieu d'éléments d'énumération, nous transmettons une constante de cette énumération, alors TypeScript comprend que ce n'est pas toute l'énumération, pas tous ses éléments. Et il y aura déjà une valeur spécifique du type, c'est-à-dire lang en, English.
Si nous passons autre chose, par exemple une chaîne, il semblerait qu'elle ait la même signification que l'énumération. Mais c'est déjà une chaîne, un autre type dans TypeScript, et nous l'obtiendrons. Et si nous passons une chaîne comme constante, alors au lieu d'une chaîne, il y aura une constante, une chaîne littérale, ce ne sont pas toutes des chaînes. Dans notre cas, il y aura une chaîne spécifique en.
Voyons maintenant comment nous pouvons étendre cela.
Nous avions un paramètre. Rien ne nous empêche d'utiliser plusieurs paramètres. Tous sont écrits séparés par des virgules. Dans les mêmes crochets angulaires, et nous les appliquons dans l'ordre - du premier au troisième. Nous substituons les valeurs souhaitées lors de l'appel.
Disons une concaténation de littéraux numériques, un type standard, une concaténation de littéraux de chaîne. Ils sont tous simplement écrits dans l'ordre.
Voyons comment cela se produit dans les fonctions. Nous créons une fonction aléatoire. Il donne aléatoirement le premier argument ou le second.
Le premier argument est de type A, le second est de type B. En conséquence, leur union est renvoyée: soit ceci, soit ceci. Tout d'abord, nous pouvons taper explicitement la fonction. Nous indiquons que A est une chaîne, B est un nombre. TypeScript examinera ce que nous avons explicitement spécifié et en inférera le type.
Mais nous pouvons également utiliser l'inférence de type. L'essentiel est de savoir que ce n'est pas seulement le type qui est déduit, mais le plus petit type possible pour l'argument.
Supposons que nous passions un argument, un littéral de chaîne, et il doit correspondre au type A, et le deuxième argument, un, au type B. Le minimum possible pour un littéral de chaîne et un est le littéral A et le même. TypeScript nous le fournira. Il s'avère un tel rétrécissement des types.
Avant de passer aux exemples suivants, nous verrons comment les types sont généralement liés les uns aux autres, comment utiliser ces relations, comment mettre de l'ordre dans le chaos de tous les types.
Relation des types
Les types peuvent être classiquement considérés comme une sorte d'ensemble. Regardons le diagramme, qui montre un morceau de l'ensemble des types.
Nous voyons que les types qu'il contient sont liés par une sorte de relation. Mais lesquels? Ce sont des relations d'ordre partiel - ce qui signifie qu'un type est toujours spécifié avec son supertype, c'est-à-dire un type "au-dessus", qui couvre toutes les valeurs possibles.
Si vous allez dans la direction opposée, alors chaque type peut avoir un sous-type, "moins" de celui-ci.
Quels sont les supertypes d'une chaîne? Toutes les jointures qui incluent une chaîne. Une chaîne avec un nombre, une chaîne avec un tableau de nombres, peu importe. Les sous-types sont tous des littéraux de chaîne: a, b, c ou ac ou ab.
Mais il est important de comprendre que l'ordre n'est pas linéaire. Autrement dit, tous les types ne peuvent pas être comparés. C'est logique, et c'est ce qui conduit à des erreurs d'incompatibilité de type. Autrement dit, une chaîne ne peut pas être simplement comparée à un nombre.
Et dans cet ordre, il y a un type, pour ainsi dire, le plus haut - inconnu. Et l'analogue le plus bas de l'ensemble vide n'est jamais. Jamais n'est un sous-type d'aucun type. Et inconnu est un supertype de n'importe quel type.
Et, bien sûr, il y a une exception - aucune. C'est un type spécial, il ignore complètement cet ordre et est utilisé si nous migrons de JavaScript pour ne pas nous soucier des types. Il n'est pas recommandé d'en utiliser à partir de zéro. Cela vaut la peine de le faire si nous ne nous soucions pas vraiment de la position du type dans cet ordre.
Voyons ce que la connaissance de cet ordre nous donnera.
Nous pouvons restreindre les paramètres à leurs supertypes. Le mot-clé est étend. Nous définirons un type, générique, qui n'aura qu'un seul paramètre. Mais nous dirons qu'il ne peut s'agir que d'un sous-type de la chaîne ou de la chaîne elle-même. Nous ne pourrons pas transférer les numéros, cela entraînera une erreur de type. Si nous tapons explicitement la fonction, alors dans les paramètres, nous ne pouvons spécifier que les sous-types de la chaîne ou de la chaîne - pomme et orange. Les deux chaînes sont une concaténation de littéraux de chaîne. Vérification réussie.
Nous pouvons également déduire automatiquement les types nous-mêmes en fonction des arguments. Si nous avons passé un littéral de chaîne, alors c'est aussi une chaîne. Le chèque a fonctionné.
Voyons comment étendre ces restrictions.
Nous nous sommes limités à une seule ligne. Mais une chaîne est un type trop simple. Je voudrais travailler avec des clés d'objet. Pour travailler avec eux, nous comprenons d'abord comment les clés d'objet elles-mêmes et leurs types sont arrangées.
Nous avons un certain objet. Il a une sorte de champs: chaînes, nombres, valeurs booléennes et clés par nom. Pour obtenir les clés, nous utilisons le mot clé keyof. Nous obtenons l'union de tous les noms de clé.
Si nous voulons obtenir les valeurs, nous pouvons le faire via la syntaxe des crochets. Ceci est similaire à la syntaxe JS. Il ne renvoie que les types. Si nous passons le sous-ensemble entier des clés, alors nous obtenons l'union de toutes les valeurs de cet objet en général.
Si nous voulons obtenir une partie, nous pouvons le spécifier - pas toutes les clés, mais un sous-ensemble. Nous nous attendons à recevoir uniquement les champs correspondant aux clés spécifiées. Si nous réduisons tout à un seul cas, il s'agit d'un champ et une clé donne une valeur. De cette façon, vous pouvez obtenir le champ correspondant.
Voyons comment utiliser les clés d'objet.
Il est important de comprendre qu'il peut y avoir n'importe quel type valide après le mot clé extend. Y compris formé à partir d'autres génériques ou à l'aide de mots-clés.
Voyons comment cela fonctionne avec keyof. Nous avons défini le type CustomPick. En fait, il s'agit presque d'une copie complète du type de bibliothèque Pick de TypeScript. Que fait-il?
Il a deux paramètres. Le second n'est pas qu'un paramètre. Ce doit être les clés du premier. Nous voyons que nous avons l'extension keyof de <T>. Par conséquent, il doit s'agir d'un sous-ensemble des clés.
Ensuite, pour chaque clé K de ce sous-ensemble, nous courons autour de l'objet, mettons la même valeur et supprimons spécialement le caractère facultatif, moins le point d'interrogation avec la syntaxe. Autrement dit, tous les champs seront obligatoires.
Nous regardons l'application. Nous avons un objet, en lui les noms des champs. Nous ne pouvons en prendre qu'un sous-ensemble - a, b ou c, ou tous à la fois. Nous avons pris un ou un c. Seules les valeurs correspondantes sont affichées, mais nous voyons que le champ a est devenu obligatoire, car nous avons, relativement parlant, supprimé le point d'interrogation. Nous avons défini ce type, l'avons utilisé. Personne ne nous dérange de prendre ce générique et de le pousser dans un autre générique.
Comment cela peut-il arriver? Nous avons défini un autre type, Custom. Le deuxième paramètre ne développe pas keyof, mais le résultat de l'application du générique, que nous avons montré à droite. Comment ça marche, qu'est-ce qu'on y transfère?
On passe n'importe quel objet et toutes ses clés à ce générique. Cela signifie que la sortie sera une copie de l'objet avec tous les champs obligatoires. Cette chaîne d'imbrication d'un générique dans un autre générique et ainsi de suite peut se poursuivre indéfiniment, en fonction des tâches, et structurer le code. Introduisez des constructions réutilisables dans les génériques, etc.
Les arguments spécifiés ne doivent pas nécessairement être dans l'ordre. Un peu comme le paramètre P étend les clés T dans le générique CustomPick. Mais personne ne nous a dérangés de l'indiquer comme premier paramètre et T comme second. TypeScript ne passe pas séquentiellement à travers les paramètres. Il examine tous les paramètres que nous avons spécifiés. Ensuite, il résout un certain système d'équations, et s'il trouve une solution aux types qui satisfont ce système, alors la vérification de type est réussie.
À cet égard, vous pouvez dériver un générique aussi amusant, dans lequel les paramètres élargissent les clés de l'autre: a - ce sont les clés b, b - les clés a. Il semblerait, comment est-ce possible, les clés des clés? Mais nous savons que les chaînes TypeScript sont en fait des chaînes JavaScript et que les chaînes JavaScript ont leurs propres méthodes. En conséquence, n'importe quel nom de méthode de chaîne fera l'affaire. Parce que le nom d'une méthode de chaîne est également une chaîne. Et à partir de là, elle porte son nom.
En conséquence, nous pouvons obtenir une telle restriction, et le système d'équations sera résolu si nous indiquons les types requis.
Voyons comment cela peut être utilisé dans la réalité. Nous l'utilisons pour l'API. Il existe un site sur lequel les applications Yandex sont déployées. Nous voulons afficher le projet et le service qui lui correspond.
Dans l'exemple, j'ai pris un projet pour exécuter des machines virtuelles qyp pour les développeurs. Nous savons que nous avons la structure de cet objet dans le backend, nous la prenons à la base. Mais à côté du projet, il y a d'autres objets: brouillons, ressources. Et ils ont tous leurs propres structures.
De plus, nous voulons demander non pas l'objet entier, mais quelques champs - le nom et le nom du service. Il y a une telle opportunité, le backend vous permet de passer des chemins et de recevoir une structure incomplète. DeepPartial est décrit ici. Nous apprendrons à le concevoir un peu plus tard. Mais cela signifie que non pas l'objet entier est transféré, mais une partie de celui-ci.
Nous voulons écrire une fonction qui demanderait ces objets. Écrivons en JS. Mais si vous regardez de plus près, vous pouvez voir des fautes de frappe. Dans le type de "Projeact", dans les chemins, il y a aussi une faute de frappe dans le service. Pas bon, l'erreur sera en cours d'exécution.
La variante TS ne semble pas être très différente en dehors des chemins. Mais nous montrerons qu'en fait, il ne peut y avoir d'autres valeurs dans le champ Type que celles que nous avons sur le backend.
Le champ des chemins a une syntaxe spéciale qui ne nous permet tout simplement pas de sélectionner d'autres champs manquants. Nous utilisons une fonction où nous listons simplement les niveaux d'imbrication dont nous avons besoin et obtenons un objet. En fait, obtenir des chemins à partir de cette fonction est une préoccupation de notre implémentation. Il n'y a pas de secret ici, elle utilise un proxy. Ce n'est pas si important pour nous.
Voyons comment obtenir la fonction.
Nous avons une fonction, son utilisation. Il y a cette structure. Premièrement, nous voulons obtenir tous les noms. Nous écrivons un type où le nom correspond à la structure.
Disons que pour un projet, nous décrivons son type quelque part. Dans notre projet, nous générons des taipings à partir de fichiers protobuf disponibles dans le référentiel général. Ensuite, nous voyons que nous avons tous les types utilisés: Projet, Brouillon, Ressource.
Regardons l'implémentation. Regardons cela dans l'ordre.
Il y a une fonction. Voyons d'abord comment il est paramétré. Juste par ces noms précédemment décrits. Voyons ce qu'il renvoie. Il renvoie des valeurs. Pourquoi cela est-il ainsi? Nous avons utilisé la syntaxe des crochets. Mais puisque nous passons une chaîne au type, la concaténation des littéraux de chaîne lorsqu'elle est utilisée est toujours une chaîne. Il n'est pas possible de composer une chaîne qui soit à la fois un projet et une ressource. Elle est toujours une, et le sens est également le même.
Emballons tout dans DeepPartial. Type facultatif, structure facultative. Le plus intéressant, ce sont les paramètres. Nous leur demandons à l'aide d'un autre générique.
Le type avec lequel les paramètres génériques sont paramétrés correspond également à la contrainte sur la fonction. Il ne peut accepter que le type de nom - Projet, Ressource, Brouillon. ID est, bien sûr, une chaîne, cela ne nous intéresse pas. Voici le type que nous avons indiqué, l'un des trois. Je me demande comment fonctionne la fonction de chemin. Ceci est un autre générique - pourquoi ne pas le réutiliser. En fait, tout ce qu'il fait, c'est simplement créer une fonction qui renvoie un tableau de any, car notre objet peut avoir des champs de n'importe quel type, nous ne savons pas lesquels. Dans cette implémentation, nous acquérons le contrôle sur les types.
Si quelqu'un a trouvé cela simple, passons aux structures de contrôle.
Constructions de contrôle
Nous ne considérerons que deux constructions, mais elles suffiront à couvrir presque toutes les tâches dont nous avons besoin.
Quels sont les types conditionnels? Ils sont très similaires aux ternarks en JavaScript, uniquement pour les types. Nous avons une condition que le type a est un sous-type de b. Si tel est le cas, retournez c. Sinon, retournez d. Autrement dit, il s'agit d'un si normal, uniquement pour les types.
Voyons voir comment ça fonctionne. Nous allons définir un type CustomExclude, qui copie essentiellement la bibliothèque Exclude. Il jette simplement les éléments dont nous avons besoin de l'union de types. Si a est un sous-type de b, alors retourne vide, sinon renvoie a. C'est étrange quand vous regardez pourquoi cela fonctionne avec les jointures.
Une loi spéciale est utile, qui dit: s'il y a une union et que nous vérifions les conditions à l'aide d'étend, nous vérifions chaque élément séparément et les combinons à nouveau. Il s'agit d'une telle loi transitive, uniquement pour les types conditionnels.
Lorsque nous utilisons CustomExclude, nous examinons chaque élément d'observation à tour de rôle. a développe a, a est un sous-type, mais renvoie void; b est un sous-type de a? Non - retour b. c n'est pas non plus un sous-type de a, retourne c. Ensuite, nous combinons ce qui reste, tous les signes plus, nous obtenons b et c. Nous avons jeté un et avons obtenu ce que nous voulions.
La même technique peut être utilisée pour obtenir toutes les clés d'un tuple. Nous savons qu'un tuple est le même tableau. Autrement dit, il a des méthodes JS, mais nous n'en avons pas besoin, nous n'avons besoin que d'index. En conséquence, nous jetons simplement les noms de toutes les méthodes de toutes les clés du tuple et n'obtenons que les indices.
Comment définissons-nous notre type DeepPartial mentionné précédemment? C'est là que la récursivité est utilisée pour la première fois. Nous parcourons toutes les clés de l'objet et regardons. La valeur est-elle un objet? Si tel est le cas, appliquez-le de manière récursive. Sinon, et s'il s'agit d'une chaîne ou d'un nombre, laissez-le et rendez tous les champs facultatifs. C'est toujours un type partiel.
Cet appel récursif et les types conditionnels rendent en fait TypeScript Turing complet. Mais ne vous précipitez pas pour vous en réjouir. Cela vous fait chier si vous essayez de faire quelque chose comme ça, une abstraction avec beaucoup de récursivité.
TypeScript surveille cela et renvoie une erreur même au niveau de son compilateur. Vous n'attendrez même pas que quelque chose y soit compté. Et pour des cas aussi simples, où nous n'avons qu'un seul appel, la récursivité est tout à fait appropriée.
Voyons voir comment ça fonctionne. Nous voulons résoudre le problème de la correction du champ de l'objet. Nous utilisons un cloud virtuel pour planifier le déploiement des applications et nous avons besoin de ressources.
Disons que nous avons pris des ressources CPU, des cœurs. Tout le monde a besoin de noyaux. J'ai simplifié l'exemple, et il n'y a que des ressources, que des noyaux, et ce sont des nombres.
Nous voulons créer une fonction qui les corrige, corrige les valeurs. Ajoutez des noyaux ou soustrayez. Dans le même JavaScript, comme vous l'avez peut-être deviné, il y a des fautes de frappe. Ici, nous ajoutons un nombre à une chaîne - pas très bon.
Presque rien n'a changé dans TypeScript, mais en fait, ce contrôle au niveau de l'EDI vous dira que vous ne pouvez rien passer d'autre que cette chaîne ou un nombre spécifique.
Voyons comment y parvenir. Nous devons obtenir une telle fonction, et nous savons que nous avons un objet de ce genre. Vous devez comprendre que nous ne corrigeons que le nombre et les champs. Autrement dit, vous ne devez obtenir le nom que des champs contenant des nombres. Nous n'avons qu'un seul champ, et c'est un nombre.
Voyons comment cela est implémenté dans TypeScript.
Nous avons défini une fonction. Il n'a que trois arguments, l'objet que nous corrigeons et le nom du champ. Mais ce n'est pas seulement un nom de champ. Il ne peut s'agir que du nom de champs numériques. Nous allons maintenant découvrir comment cela se fait. Et le patcher lui-même, qui est une fonction pure.
Il y a une certaine fonction impersonnelle, un patch. Nous ne sommes pas intéressés par sa mise en œuvre, mais par comment obtenir un type aussi intéressant, pour obtenir les clés non seulement numériques, mais de tous les champs par condition. Nous avons des chiffres ici.
Analysons dans l'ordre comment cela se produit.
Nous parcourons toutes les clés de l'objet passé, puis nous faisons cette procédure. Voyons que le champ objet est un sous-type de celui souhaité, c'est-à-dire un champ numérique. Si oui, alors il est important que nous n'écrivions pas la valeur du champ, mais le nom du champ, sinon, en général, rien, jamais.
Mais alors un objet aussi étrange s'est avéré. Tous les champs numériques ont commencé à avoir leurs noms sous forme de valeurs et tous les champs non numériques sont devenus vides. Ensuite, nous prenons toutes les valeurs de cet objet étrange.
Mais comme toutes les valeurs contiennent de la vacuité et que la vacuité s'effondre une fois combinées, seuls les champs qui correspondent aux champs numériques restent. Autrement dit, nous n'avons que les champs obligatoires.
L'exemple montre: il y a un objet simple, le champ en est un. Est-ce un nombre? Oui. Le champ est un nombre, est-ce un nombre? Oui. La dernière ligne n'est pas un nombre. Nous n'obtenons que les champs numériques nécessaires.
Avec cela réglé. J'ai laissé le plus difficile pour la fin. C'est l'inférence de type - Infer. Capture d'un type dans une construction conditionnelle.
Il est indissociable de la rubrique précédente, car il ne fonctionne qu'avec une construction conditionnelle.
À quoi cela ressemble-t-il? Disons que nous voulons connaître les éléments d'un tableau. Un certain type de tableau est venu, nous aimerions connaître un élément spécifique. Nous regardons: nous avons reçu une sorte de tableau. C'est un sous-type du tableau de la variable x. Si oui - renvoie cet élément x, tableau. Sinon, retournez le vide.
Dans cette condition, la deuxième branche ne sera jamais exécutée, car nous avons paramétré le type avec n'importe quel tableau. Bien sûr, ce sera un tableau de quelque chose, car un tableau de n'importe lequel ne peut pas ne pas avoir d'éléments.
Si nous transmettons un tableau de chaînes, une chaîne devrait nous être renvoyée. Et il est important de comprendre que ce n'est pas seulement un type qui est défini ici. C'est visuellement clair d'après le tableau de chaînes: il y a des chaînes. Mais avec un tuple, tout n'est pas si simple. Il est important pour nous de savoir que le plus petit supertype possible est en cours de détermination. Il est clair que tous les tableaux sont, pour ainsi dire, des sous-types d'un tableau avec any ou avec unknown. Cette connaissance ne nous donne rien. Il est important pour nous de connaître le minimum possible.
Supposons que nous passions dans un tuple. En fait, les tuples sont aussi des tableaux, mais comment dire quels éléments ce tableau a? S'il existe un tuple de chaîne d'un nombre, il s'agit en fait d'un tableau. Mais l'élément doit être du même type. Et s'il y a à la fois une chaîne et un nombre, alors il y aura une union.
TypeScript affichera ceci, et nous obtiendrons exactement la concaténation d'une chaîne et d'un nombre pour un tel exemple.
Vous pouvez utiliser non seulement la capture en un seul endroit, mais également autant de variables que vous le souhaitez. Disons que nous définissons un type qui échange simplement les éléments du tuple: le premier avec le second. Nous prenons le premier élément, le second et les échangeons.
Mais en fait, il n'est pas recommandé de trop flirter avec lui. Habituellement, pour 90% des tâches, un seul type de capture suffit.
Voyons un exemple. Objectif: vous devez montrer, selon l'état de la demande, soit une bonne option, soit une mauvaise. Voici des captures d'écran de notre service de déploiement d'applications. Une entité, ReplicaSet. Si la requête du backend a renvoyé une erreur, vous devez la restituer. En même temps, il existe une API pour le backend. Voyons ce que Infer a à voir avec cela.
Nous savons que nous utilisons, d'une part, redux et, d'autre part, redux thunk. Et nous devons transformer le thunk de la bibliothèque pour pouvoir le faire. Nous avons un mauvais et un bon.
Et nous savons qu'une bonne façon d'aller dans extraReducers dans la boîte à outils redux ressemble à ceci. Nous savons qu'il y a PayLoad, et nous voulons extraire les types personnalisés qui nous sont parvenus du backend, mais pas seulement, mais aussi des informations sur une bonne ou une mauvaise requête: y a-t-il une erreur ou non. Nous avons besoin d'un générique pour cette sortie.
Je ne fais pas de comparaison sur JavaScript car cela n'a pas de sens. En JavaScript, en principe, vous ne pouvez en aucun cas contrôler les types et ne compter que sur la mémoire. Il n'y a pas de mauvaise option ici, car il n'y en a tout simplement pas.
Nous savons que nous voulons ce type. Mais nous n'avons pas qu'une action. Nous devons appeler dispatch avec cette action. Et nous avons besoin de cette vue, où nous devons afficher une erreur par la clé de demande. Autrement dit, vous devez mélanger ces fonctionnalités supplémentaires en plus de redux thunk à l'aide de la méthode withRequestKey.
Nous avons cette méthode, bien sûr, mais nous avons également la méthode API d'origine, getReplicaSet. Il est écrit quelque part et nous devons remplacer le redux thunk en utilisant une sorte d'adaptateur. Voyons comment le faire.
Nous devons obtenir une fonction comme celle-ci. C'est un bruit sourd avec tellement de fonctionnalités supplémentaires. Cela semble effrayant, mais ne vous inquiétez pas, nous allons maintenant le démonter sur les étagères pour qu'il soit clair.
Il existe un adaptateur qui étend le type de bibliothèque d'origine. Nous mélangeons simplement une méthode withRequestKey supplémentaire et un appel personnalisé à ce type de bibliothèque. Voyons quelle est la caractéristique principale du générique, quels paramètres sont utilisés.
Le premier est juste notre API, un objet avec des méthodes. Nous pouvons faire getReplicaSet, obtenir des projets, des ressources, peu importe. Nous utilisons une méthode spécifique dans la méthode actuelle, et le deuxième paramètre est simplement le nom de la méthode. Ensuite, nous utilisons les paramètres de la fonction que nous demandons, nous utilisons le type de bibliothèque Paramètres, c'est un type TypeScript. Et de même, nous utilisons le type de bibliothèque ReturnType pour la réponse backend. C'est pour ce que la fonction a renvoyé.
Ensuite, nous passons simplement notre sortie personnalisée dans le type AsyncThunk que la bibliothèque nous a fourni. Mais quelle est cette conclusion? Ceci est un autre médicament générique. En fait, cela semble simple. Nous sauvegardons non seulement la réponse du serveur, mais aussi nos paramètres, ce que nous avons passé. Juste pour les suivre dans Reducer. Ensuite, nous regardons withRequestKey. Notre méthode ajoute simplement une clé. Que revient-il? Le même adaptateur car nous pouvons le réutiliser. Nous n'avons pas du tout besoin d'écrire avecRequestKey. Ce n'est qu'une fonctionnalité supplémentaire. Il encapsule et nous renvoie récursivement le même adaptateur, et nous y passons la même chose.
Enfin, voyons comment sortir vers Reducer ce que ce thunk nous a renvoyé.
Nous avons cet adaptateur. L'essentiel est de se rappeler qu'il existe quatre paramètres: l'API, la méthode API, les paramètres (entrée) et la sortie. Nous devons trouver une issue. Mais nous nous souvenons que nous avons une sortie personnalisée: à la fois la réponse du serveur et le paramètre de requête.
Comment puis-je faire cela avec Infer? On voit que cet adaptateur est fourni à l'entrée, mais il est généralement n'importe lequel: n'importe lequel, n'importe lequel, n'importe lequel, n'importe lequel. Nous devons renvoyer ce type, il ressemble à ceci, la réponse du serveur et les paramètres de la requête. Et nous cherchons à savoir où devrait se trouver l'entrée. Au troisieme. C'est là que nous plaçons notre capture de type. Nous obtenons l'entrée. De même, la sortie est à la quatrième place.
TypeScript est basé sur un typage structuré. Il démonte cette structure et comprend que l'entrée est ici, à la troisième place, et la sortie est à la quatrième. Et nous retournons les types que nous voulons.
Nous avons donc réalisé l'inférence de type, nous y avons déjà accès dans le réducteur lui-même. Il est fondamentalement impossible de faire cela en JavaScript.