Comment démarrer rapidement avec VUE3.0 : Entrez et apprenez
Nest fournit un mécanisme de module. L'injection de dépendances est complétée par la définition des fournisseurs, des importations, des exportations et des constructeurs de fournisseurs dans le décorateur de module, et le développement de l'ensemble de l'application est organisé via le. arborescence des modules. Il n’y a absolument aucun problème à lancer directement une application selon les conventions du framework lui-même. Cependant, pour moi, j'estime qu'il me manque une compréhension plus claire et systématique de l'injection de dépendances, de l'inversion de contrôle, des modules, des fournisseurs, des métadonnées, des décorateurs associés, etc. déclarés par le framework.
- Pourquoi une inversion du contrôle est-elle nécessaire ?
- Qu’est-ce que l’injection de dépendances ?
- Que fait le décorateur ?
- Quels sont les principes de mise en œuvre des fournisseurs, des importations et des exportations dans les modules (@Module) ?
Il me semble être capable de le comprendre et de l'apprécier, mais laissez-moi l'expliquer clairement dès le début, je ne peux pas l'expliquer clairement. J'ai donc fait quelques recherches et j'ai rédigé cet article. A partir de maintenant, nous partons de zéro et entrons dans le texte principal.
1.1 Express, Koa
Le processus de développement d'un langage et de sa communauté technique doit progressivement s'enrichir et se développer depuis les fonctions de base vers le haut, tout comme le processus des racines des arbres grandissant lentement en branches puis se remplissant de feuilles. Auparavant, des frameworks de services Web de base tels que Express et Koa sont apparus dans Nodejs. Capable de fournir une capacité de service très basique. Sur la base d'un tel framework, un grand nombre de middlewares et de plug-ins ont commencé à naître dans la communauté, fournissant des services plus riches pour le framework. Nous devons organiser les dépendances des applications et créer nous-mêmes un échafaudage d'applications, qui est flexible et lourd, et nécessite également une certaine charge de travail.
Plus tard dans le développement, certains cadres avec une production plus efficace et des règles plus unifiées sont nés, ouvrant la voie à une nouvelle étape.
1.2 EggJs et Nestjs
Afin d'être plus adaptables aux applications de production rapide, d'unifier les normes et de les rendre disponibles immédiatement, des frameworks tels que EggJs, NestJs et Midway ont été développés. Ce type de framework résume l'implémentation d'une application en un processus universel et extensible en implémentant le cycle de vie sous-jacent. Il suffit de suivre la méthode de configuration fournie par le framework pour implémenter l'application plus simplement. Le cadre met en œuvre le contrôle des processus du programme, et il nous suffit d'assembler nos pièces à l'endroit approprié. Cela ressemble davantage à un travail à la chaîne. Chaque processus est clairement divisé et de nombreux coûts de mise en œuvre sont économisés.
1.3 Résumé
Les deux étapes ci-dessus ne sont qu'une préfiguration.Nous pouvons comprendre à peu près que la mise à niveau du framework améliore l'efficacité de la production, certaines idées et modèles de conception seront introduits dans Nest. l'injection de dépendances et les concepts de métaprogrammation, parlons-en ci-dessous.
2.1 Injection de dépendances
Une application est en fait un ensemble de classes abstraites, qui réalisent toutes les fonctions de l'application en s'appelant les unes les autres. À mesure que la complexité du code et des fonctions de l'application augmente, le projet deviendra certainement de plus en plus difficile à maintenir car il y a de plus en plus de classes et les relations entre elles deviennent de plus en plus complexes.
Par exemple, si nous utilisons Koa pour développer notre application, Koa lui-même implémente principalement un ensemble de fonctionnalités de base du service Web. Dans le processus d'implémentation de l'application, nous définirons de nombreuses classes, les méthodes d'instanciation et les interdépendances de ces classes. être librement organisé et contrôlé par nos soins dans la logique du code. L'instanciation de chaque classe est nouvelle manuellement par nous, et nous pouvons contrôler si une classe n'est instanciée qu'une seule fois puis partagée, ou si elle est instanciée à chaque fois. La classe B suivante dépend de A. Chaque fois que B est instancié, A sera instancié une fois, donc pour chaque instance B, A est une instance qui n'est pas partagée.
classe A{} //B classe B{ constructeur(){ this.a = nouveau A(); } }
Le C ci-dessous est l'instance externe obtenue, donc plusieurs instances C partagent l'instance app.a.
classe A{} //C const application = {} ; app.a = nouveau A(); classe C{ constructeur(){ ceci.a = app.a; } }
Le D suivant est transmis via le paramètre constructeur. Vous pouvez transmettre une instance non partagée à chaque fois, ou vous pouvez transmettre l'instance app.a partagée (D et F partagent app.a), et à cause de la manière. c'est maintenant un paramètre Pass in, je peux aussi passer une instance de classe X.
classe A{} classe X{} //D const application = {} ; app.a = nouveau A(); classe D{ constructeur(a){ ceci.a = a; } } classe F{ constructeur(a){ ceci.a = a; } } nouveau D(app.a) nouveau F(app.a)
nouveau
D (nouveau
L'injection via le constructeur (passage par valeur) n'est qu'une méthode d'implémentation. Elle peut également être transmise en implémentant l'appel de la méthode set, ou toute autre méthode, à condition qu'une dépendance externe puisse être transmise à la dépendance interne. C'est vraiment aussi simple que cela.
classe A{} //D classe D{ setDep(a){ ceci.a = a; } } const d = nouveau D() d.setDep(new A())
2.2 Tout en injection de dépendances ?
Au fur et à mesure de l'itération, il apparaît que les dépendances de B changeront en fonction de différentes conditions préalables. Par exemple, une condition préalable this.a
doit être transmise dans l'instance de A, et une condition préalable deux this.a
doit être transmise dans l'instance de X. À ce stade, nous commencerons à faire l’abstraction proprement dite. Nous allons le transformer en une méthode d'injection de dépendances comme D ci-dessus.
Au début, lorsque nous implémentions l'application, nous implémentions la méthode d'écriture des classes B et C tant qu'elle répondait aux besoins du moment. Cela en soi n'était pas un problème. Après que le projet ait itéré pendant plusieurs années, cette partie. du code ne serait pas nécessairement touché. Si nous envisageons une expansion ultérieure, cela affectera l’efficacité du développement et pourrait ne pas être utile. Ainsi, la plupart du temps, nous rencontrons des scénarios qui nécessitent une abstraction, puis transformons abstraitement une partie du code.
// classe B{ avant transformation constructeur(){ this.a = nouveau A(); } } nouveau B() //Classe D{ après transformation constructeur(a){ ceci.a = a; } } nouveau D (nouveau A ())nouveau D
(
nouveauxcoûts de mise en œuvre.
Cet exemple est donné ici pour illustrer cela dans un modèle de développement sans aucune contrainte ni réglementation. Nous pouvons librement écrire du code pour contrôler les dépendances entre différentes classes. Dans un environnement complètement ouvert, c'est très libre. C'est une époque primitive de l'agriculture sur brûlis. Puisqu'il n'y a pas de modèle de développement de code fixe ni de plan d'action élevé, à mesure que différents développeurs interviennent ou que le même développeur écrit du code à des moments différents, la relation de dépendance deviendra très différente à mesure que le code grandit. De toute évidence, l'instance partagée peut être instanciée plusieurs fois. , gaspillant de la mémoire. À partir du code, il est difficile de voir une structure de dépendance complète et le code peut devenir très difficile à maintenir.
Ensuite, chaque fois que nous définissons une classe, nous l'écrivons selon la méthode d'injection de dépendances et l'écrivons comme D. Ensuite, le processus d'abstraction de C et B est avancé, ce qui rend l'expansion ultérieure plus pratique et réduit le coût de transformation. C'est ce qu'on appelle All in 依赖注入
, c'est-à-dire que toutes nos dépendances sont implémentées via l'injection de dépendances.
Cependant, le coût de mise en œuvre initial redevient élevé et il est difficile d'atteindre l'unité et la persistance dans la collaboration en équipe. En fin de compte, la mise en œuvre peut également être définie comme une conception excessive, car le coût de mise en œuvre supplémentaire peut ne pas être atteint. nécessairement être atteint.
2.3 Inversion du contrôle
Maintenant que nous sommes convenus de l'utilisation unifiée de l'injection de dépendances, pouvons-nous implémenter un contrôleur de niveau inférieur via l'encapsulation sous-jacente du framework et convenir d'une règle de configuration des dépendances ? configuration des dépendances que nous avons définie et partage des dépendances pour nous aider à réaliser la gestion des classes. Ce modèle de conception est appelé inversion de contrôle .
L’inversion du contrôle peut être difficile à comprendre lorsque vous l’entendez pour la première fois. Que signifie le contrôle ? Qu'est-ce qui a été inversé ?
On suppose que cela est dû au fait que les développeurs ont utilisé de tels frameworks depuis le début et n'ont pas connu la dernière « ère Express et Koa » et n'ont pas subi les coups de l'ancienne société. Associé à la formulation inversée, le programme semble très abstrait et difficile à comprendre.
Comme nous l'avons mentionné précédemment, lors de la mise en œuvre d'applications Koa, toutes les classes sont entièrement contrôlées par nous, cela peut donc être considéré comme une méthode de contrôle de programme conventionnelle, c'est pourquoi nous l'appelons : contrôler la rotation vers l'avant. Nous utilisons Nest, qui implémente un ensemble de contrôleurs en bas. Il nous suffit d'écrire le code de configuration conformément à l'accord pendant le processus de développement réel, et le programme-cadre nous aidera à gérer l'injection de dépendances des classes, nous l'appelons donc : inversion du contrôle.
L'essentiel est de confier le processus de mise en œuvre du programme au programme-cadre pour une gestion unifiée et de transférer le pouvoir de contrôle du développeur au programme-cadre.
Contrôler la rotation avant : programme de contrôle purement manuel du développeur
Inversion de contrôle : contrôle du programme-cadre
Pour donner un exemple concret, une personne se rend seule à son travail et son objectif est de rejoindre l'entreprise. Il se conduit lui-même et contrôle son propre itinéraire. Et s'il cède le contrôle de la conduite et prend le bus, il lui suffit de choisir une navette correspondante pour rejoindre l'entreprise. Rien qu'en termes de contrôle, les gens sont libérés. Il leur suffit de se rappeler quel bus prendre. Le risque de commettre des erreurs est également réduit et les gens sont beaucoup plus détendus. Le système de bus est le contrôleur et les lignes de bus sont les configurations convenues.
Grâce à la comparaison réelle ci-dessus, je pense que je devrais être capable de comprendre l’inversion du contrôle.
2.4 Résumé
De Koa à Nest, du front-end JQuery à Vue React. En fait, ils sont tous mis en œuvre étape par étape via l'encapsulation du framework pour résoudre les problèmes d'inefficacité de l'ère précédente.
Le développement d'applications Koa ci-dessus utilise une manière très primitive de contrôler les dépendances et l'instanciation, qui est similaire au système d'exploitation JQuery dans le front-end. Cette méthode très primitive est appelée transfert de contrôle, et Vue React est comme ce que Nest fournit. contrôleur, ils peuvent tous être appelés inversion de contrôle. C'est aussi ma compréhension personnelle. S'il y a des problèmes, j'espère que Dieu les signalera.
Parlons du module @Module dans Nest. L'injection de dépendances et l'inversion de contrôle l'exigent comme média.
Nestjs implémente l'inversion de contrôle et s'engage à configurer les importations, exportations et fournisseurs du module (@module) pour gérer le fournisseur, qui est l'injection de dépendances de la classe.
Les fournisseurs peuvent être compris comme enregistrant et instanciant des classes dans le module actuel. Les éléments A et B suivants sont instanciés dans le module actuel. Si B fait référence à A dans le constructeur, il fait référence à l'instance A du ModuleD actuel.
importer { Module } depuis '@nestjs/common' ; importer { ModuleX } depuis './moduleX' ; importer { A } depuis './A' ; importer {B} depuis './B' ; @Module({ importations : [ModuleX], fournisseurs : [A,B], exportations : [A] }) classe d'exportation ModuleD {} //B classe B{ constructeur(a:A){ ceci.a = a; } }
exports
font référence aux classes instanciées dans providers
du module actuel en tant que classes pouvant être partagées par des modules externes. Par exemple, lorsque la classe C de ModuleF est instanciée, je souhaite injecter directement l'instance de classe A de ModuleD. Définissez simplement les exportations A dans ModuleD et importez ModuleD via imports
dans ModuleF.
Selon la méthode d'écriture suivante, le programme d'inversion du contrôle analysera automatiquement les dépendances. Vérifiez d'abord s'il existe un fournisseur A dans les fournisseurs de votre propre module. Sinon, recherchez une instance de A dans le ModuleD importé. trouvé, récupérez le A de l'instance ModuleD qui est injecté dans l'instance C.
importer { Module } depuis '@nestjs/common' ; importer {ModuleD} depuis './moduleD' ; importer { C } depuis './C' ; @Module({ importations : [ModuleD], fournisseurs : [C], }) classe d'exportation ModuleF {} //C classe C { constructeur(a:A){ ceci.a = a; } }
Par conséquent, si vous souhaitez qu'un module externe utilise l'instance de classe du module actuel, vous devez d'abord définir la classe d'instanciation dans providers
du module actuel, puis définir et exporter cette classe, sinon une erreur sera signalée.
//Corriger @Module({ fournisseurs : [A], exportations : [A] }) //Erreur @Module({ fournisseurs : [], exportations : [A] })
En regardant le processus de recherche d'instances dudernier module supplémentaire
, c'est en effet un peu flou. Le point essentiel est que les classes des fournisseurs seront instanciées et, après instanciation, elles deviendront des fournisseurs. Seules les classes des fournisseurs du module seront instanciées, et l'exportation et l'importation ne sont que des configurations de relations organisationnelles. Le module donnera la priorité à l'utilisation de son propre fournisseur. Sinon, vérifiez si le module importé a un fournisseur correspondant.
Permettez-moi de mentionner quelques points de connaissance ts
C {.
constructeur (privé a : A) { } }
Puisque TypeScript prend en charge les paramètres de constructeur (privés, protégés, publics, en lecture seule) qui doivent être implicitement et automatiquement définis en tant qu'attributs de classe (propriété Parameter), il n'est pas nécessaire d'utiliser this.a = a
. C'est ainsi que c'est écrit dans Nest.
Le concept de métaprogrammation se reflète dans le framework Nest L'inversion du contrôle et les décorateurs sont la mise en œuvre de la métaprogrammation. On peut grossièrement comprendre que l'essence de la métaprogrammation est toujours la programmation, mais il y a quelques programmes abstraits au milieu. Ce programme abstrait peut identifier les métadonnées (telles que les données d'objet dans @Module), qui sont en fait une capacité d'extension qui peut en utiliser d'autres. programmes comme données à gérer. Lorsque nous écrivons de tels programmes abstraits, nous faisons de la métaprogrammation.
4.1 Métadonnées
Les métadonnées sont souvent mentionnées dans les documents Nest. Le concept de métadonnées peut prêter à confusion lorsque vous le voyez pour la première fois. Vous devez vous y habituer et le comprendre au fil du temps, vous n'avez donc pas besoin de vous y habituer. enchevêtré.
La définition des métadonnées est la suivante : des données qui décrivent des données, principalement des informations qui décrivent des attributs de données, et peuvent également être comprises comme des données qui décrivent des programmes.
exports、providers、imports、controllers
configurés par @Module dans Nest sont tous des métadonnées , car ce sont des données utilisées pour décrire les relations entre les programmes. Ces informations ne sont pas les données réelles affichées à l'utilisateur final, mais sont lues et reconnues par le programme. programme-cadre.
4.2 Nest Decorator
Si vous examinez le code source du décorateur dans Nest, vous constaterez que presque chaque décorateur lui-même ne définit des métadonnées que via des métadonnées réfléchissantes.
@Fonction d'exportationdu décorateur Injectable
Injectable(options?: InjectableOptions): ClassDecorator { return (cible : objet) => { Reflect.defineMetadata(INJECTABLE_WATERMARK, true, cible); Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, options, cible) ; } ; }
Il y a le concept de réflexion ici, et la réflexion est relativement facile à comprendre. Prenez le décorateur @Module comme exemple pour définir providers
de métadonnées. Il transmet simplement les classes dans le tableau providers
. Lorsque le programme s'exécute réellement, les classes des providers
le feront. être automatiquement utilisé par le programme-cadre. L'instanciation devient un fournisseur et les développeurs n'ont pas besoin d'effectuer explicitement l'instanciation et l'injection de dépendances. Une classe ne devient fournisseur qu’après avoir été instanciée dans un module. Les classes chez providers
se reflètent et deviennent des prestataires. L'inversion de contrôle est la technologie de réflexion utilisée.
Un autre exemple est l'ORM (Object Relational Mapping) dans la base de données. Pour utiliser ORM, il vous suffit de définir les champs de la table, et la bibliothèque ORM convertira automatiquement les données de l'objet en instructions SQL.
const data = TableModel.build(); data.time = 1 ; data.browser = 'chrome'; data.save(); // SQL : INSERT INTO tableName (time,browser) [{"time":1,"browser":"chrome"}]
La bibliothèque ORM utilise la technologie de réflexion, de sorte que les utilisateurs n'ont qu'à prêter attention aux données du champ elles-mêmes, et l'objet est que la réflexion de la bibliothèque ORM devient une instruction d'exécution SQL. Les développeurs n'ont qu'à se concentrer sur les champs de données et n'ont pas besoin d'écrire du SQL.
4.3 Reflect-Metadata
Reflect-Metadata est une bibliothèque de réflexion que Nest utilise pour gérer les métadonnées. Reflect-metadata utilise WeakMap pour créer une instance unique globale, et définit et obtient les métadonnées de l'objet décoré (classe, méthode, etc.) via les méthodes set et get.
// Jetez simplement un oeil var _WeakMap = !usePolyfill && typeof WeakMap === "function" WeakMap : CreateWeakMapPolyfill(); var Métadonnées = new _WeakMap(); fonction définirMetadata(){ OrdinaryDefineOwnMetadata(){ GetOrCreateMetadataMap(){ var targetMetadata = Metadata.get(O); si (IsUndefined (targetMetadata)) { si (!Créer) retourner indéfini ; targetMetadata = new _Map(); Metadata.set(O, targetMetadata); } var metadataMap = targetMetadata.get(P); si (IsUndefined (metadataMap)) { si (!Créer) retourner indéfini ; métadonnéesMap = new _Map(); targetMetadata.set(P, metadataMap); } retourner métadataMap ; } } }
Reflect-Metadata stocke les métadonnées de la personne décorée dans l'objet singleton global pour une gestion unifiée. Reflect-Metadata n'implémente pas de réflexion spécifique, mais fournit une bibliothèque d'outils pour aider à la mise en œuvre de la réflexion.
, revenons aux questions précédentes.
Pourquoi une inversion du contrôle est-elle nécessaire ?
Qu’est-ce que l’injection de dépendances ?
Que fait le décorateur ?
Quels sont les principes de mise en œuvre des fournisseurs, des importations et des exportations dans les modules (@Module) ?
1 et 2 Je pense l'avoir déjà précisé. Si c'est encore un peu vague, je vous propose de revenir le relire et de consulter d'autres articles pour vous aider à comprendre les connaissances à travers la réflexion de différents auteurs.
5.1 Problème [3 4] Présentation :
Nest utilise la technologie de réflexion pour implémenter l'inversion du contrôle et fournit des capacités de métaprogrammation. Les développeurs utilisent le décorateur @Module pour décorer les classes et définir les métadonnées (fournisseursimportationsexportations), et les métadonnées sont stockées dans un fichier global. objet (en utilisant la bibliothèque de métadonnées réfléchissantes). Une fois le programme exécuté, le programme de contrôle à l'intérieur du framework Nest lit et enregistre l'arborescence des modules, analyse les métadonnées et instancie la classe pour devenir un fournisseur, et la fournit dans tous les modules conformément à la définition des fournisseursimportationsexportations dans les métadonnées du module. . Recherchez des instances (fournisseurs) d'autres classes dépendantes de la classe actuelle et injectez-les via le constructeur après les avoir trouvées.
Cet article contient de nombreux concepts et ne fournit pas d’analyse trop détaillée. Les concepts mettent du temps à être compris lentement. Si vous ne comprenez pas complètement pour le moment, ne soyez pas trop inquiet. D'accord, c'est tout. Cet article a quand même demandé beaucoup d'efforts. Les amis qui l'aiment espèrent que vous pourrez vous connecter trois fois en un seul clic.