Como começar rapidamente com o VUE3.0: Entre e aprenda
O Nest fornece um mecanismo de injeção de dependência que é concluído definindo provedores, importações, exportações e construtores de provedores no decorador de módulo, e o desenvolvimento de todo o aplicativo é organizado por meio do decorador de módulo. árvore de módulos. Não há absolutamente nenhum problema em iniciar diretamente um aplicativo de acordo com as convenções do próprio framework. Porém, para mim, sinto que me falta uma compreensão mais clara e sistemática da injeção de dependência, inversão de controle, módulos, provedores, metadados, decoradores relacionados, etc. declarados pelo framework.
- Por que a inversão de controle é necessária?
- O que é injeção de dependência?
- O que o decorador faz?
- Quais são os princípios de implementação de provedores, importações e exportações em módulos (@Module)?
Parece que consigo entender e apreciar, mas deixe-me explicar claramente desde o início, não consigo explicar claramente. Então fiz algumas pesquisas e criei este artigo. A partir de agora começamos do zero e inserimos o texto principal.
1.1 Express, Koa
O processo de desenvolvimento de uma língua e de sua comunidade técnica deve gradualmente enriquecer e desenvolver-se das funções de baixo para cima, assim como o processo das raízes das árvores crescendo lentamente em galhos e depois cheios de folhas. Anteriormente, estruturas básicas de serviços da web, como Express e Koa, apareciam no Nodejs. Capaz de fornecer uma capacidade de serviço muito básica. Com base nesse framework, um grande número de middlewares e plug-ins começaram a nascer na comunidade, fornecendo serviços mais ricos para o framework. Precisamos organizar as dependências do aplicativo e construir nós mesmos o andaime do aplicativo, que é flexível e complicado e também requer uma certa carga de trabalho.
Mais tarde no desenvolvimento, nasceram algumas estruturas com produção mais eficiente e regras mais unificadas, inaugurando uma nova fase.
1.2 EggJs e Nestjs
Para ser mais adaptável a aplicações de produção rápida, unificar padrões e disponibilizá-los imediatamente, foram desenvolvidas estruturas como EggJs, NestJs e Midway. Este tipo de framework abstrai a implementação de uma aplicação em um processo universal e extensível, implementando o ciclo de vida subjacente. Precisamos apenas seguir o método de configuração fornecido pela estrutura para implementar a aplicação de forma mais simples. A estrutura implementa o controle de processo do programa, e só precisamos montar nossas peças no local apropriado. Isso se parece mais com um trabalho de linha de montagem. Cada processo é claramente dividido e muitos custos de implementação são economizados.
1.3 Resumo
As duas etapas acima são apenas um prenúncio. Podemos entender aproximadamente que a atualização da estrutura melhora a eficiência da produção. Para alcançar a atualização da estrutura, algumas idéias e padrões de design serão introduzidos. injeção de dependência e os conceitos de metaprogramação, falaremos sobre isso a seguir.
2.1 Injeção de Dependência
Um aplicativo é na verdade um monte de classes abstratas, que realizam todas as funções do aplicativo chamando umas às outras. À medida que a complexidade do código e das funções do aplicativo aumenta, o projeto certamente se tornará cada vez mais difícil de manter porque há cada vez mais classes e os relacionamentos entre elas se tornam cada vez mais complexos.
Por exemplo, se usarmos Koa para desenvolver nosso aplicativo, o próprio Koa implementa principalmente um conjunto de recursos básicos de serviços da Web. No processo de implementação do aplicativo, definiremos muitas classes, os métodos de instanciação e as interdependências dessas classes. ser livremente organizado e controlado por nós na lógica do código. A instanciação de cada classe é nova manualmente por nós e podemos controlar se uma classe é instanciada apenas uma vez e depois compartilhada ou se é instanciada todas as vezes. A seguinte classe B depende de A. Cada vez que B é instanciado, A será instanciado uma vez, portanto, para cada instância B, A é uma instância que não é compartilhada.
classe A{} // B classe B{ construtor(){ isto.a = novo A(); } }
O C abaixo é a instância externa obtida, portanto, múltiplas instâncias C compartilham a instância app.a.
classe A{} // C aplicativo const = {}; app.a = novo A(); classe C{ construtor(){ isto.a = aplicativo.a; } }
O seguinte D é passado através do parâmetro construtor Você pode passar uma instância não compartilhada a cada vez ou pode passar a instância compartilhada app.a (D e F compartilham app.a) e por causa da maneira. agora é um parâmetro Pass in, também posso passar uma instância da classe X.
classe A{} classe X{} //D aplicativo const = {}; app.a = novo A(); classe D{ construtor(a){ isto.a = a; } } classe F{ construtor(a){ isto.a = a; } } novo D (aplicativo.a) novo F (app.a)
novo
D (novo
A injeção através do construtor (passagem por valor) é apenas um método de implementação. Também pode ser passada implementando a chamada do método set, ou qualquer outro método, desde que uma dependência externa possa ser passada para a interna. É realmente tão simples.
classe A{} //D classe D{ setDep(a){ isto.a = a; } } const d = novo D() d.setDep(new A())
2.2 Tudo em injeção de dependência?
À medida que a iteração prossegue, parece que as dependências de B mudarão de acordo com diferentes pré-condições. Por exemplo, a pré-condição um this.a
precisa passar na instância de A, e a pré-condição dois this.a
precisa passar na instância de X. Neste momento, começaremos a fazer a abstração propriamente dita. Vamos transformá-lo em um método de injeção de dependência como D acima.
No início, quando implementávamos o aplicativo, implementávamos o método de escrita das classes B e C, desde que atendesse às necessidades da época. Isso em si não era um problema. Depois que o projeto foi iterado por vários anos, esta parte. do código não seria necessariamente tocado. Se considerarmos a expansão posterior, ela afectará a eficiência do desenvolvimento e poderá não ser útil. Então, na maioria das vezes, encontramos cenários que exigem abstração e depois transformamos abstratamente parte do código.
//classe B{ antes da transformação construtor(){ isto.a = novo A(); } } novo B() //Classe D{ após transformação construtor(a){ isto.a = a; } } novo D(novo A())novo D
(
novoscustos de implementação.
Este exemplo é dado aqui para ilustrar isso num modelo de desenvolvimento sem quaisquer restrições ou regulamentações. Podemos escrever código livremente para controlar dependências entre várias classes. Num ambiente completamente aberto, é muito livre. Esta é uma era primitiva de agricultura de corte e queima. Como não existe um modelo fixo de desenvolvimento de código e nenhum plano de ação mais elevado, à medida que diferentes desenvolvedores intervêm ou o mesmo desenvolvedor escreve código em momentos diferentes, o relacionamento de dependência se tornará muito diferente à medida que o código cresce. Claramente, a instância compartilhada pode ser instanciada várias vezes. , desperdiçando memória. A partir do código, é difícil ver uma estrutura de dependência completa e o código pode se tornar muito difícil de manter.
Então, toda vez que definimos uma classe, escrevemos de acordo com o método de injeção de dependência e escrevemos como D. Em seguida, o processo de abstração de C e B é avançado, o que torna a expansão posterior mais conveniente e reduz o custo de transformação. Então isso se chama All in 依赖注入
, ou seja, todas as nossas dependências são implementadas através de injeção de dependência.
No entanto, o custo de implementação inicial torna-se novamente elevado e é difícil alcançar a unidade e a persistência na colaboração da equipa. No final, a implementação pode também ser definida como um design excessivo, porque o custo adicional de implementação pode não ser suficiente. necessariamente ser alcançado.
2.3 Inversão de controle
Agora que concordamos com o uso unificado da injeção de dependência, podemos implementar um controlador de nível inferior por meio do encapsulamento subjacente da estrutura e concordar com uma regra de configuração de dependência. O controlador controlará o processo de instanciação com base no? configuração de dependência que definimos e compartilhamento de dependência para nos ajudar a alcançar o gerenciamento de classe. Esse padrão de design é chamado de inversão de controle .
A inversão de controle pode ser difícil de entender quando você a ouve pela primeira vez. O que significa controle? O que foi revertido?
Especula-se que isso ocorre porque os desenvolvedores usaram essas estruturas desde o início e não experimentaram a última “era Express e Koa” e não possuem as surras da velha sociedade. Juntamente com o texto invertido, o programa parece muito abstrato e difícil de entender.
Como mencionamos anteriormente, ao implementar aplicações Koa, todas as classes são completamente controladas por nós, por isso pode ser considerado um método convencional de controle de programa, por isso o chamamos de: controle de rotação direta. Usamos Nest, que implementa um conjunto de controladores na parte inferior. Só precisamos escrever o código de configuração de acordo com o acordo durante o processo de desenvolvimento real, e o programa-quadro nos ajudará a gerenciar a injeção de dependência de classes, por isso o chamamos: inversão de controle.
A essência é entregar o processo de implementação do programa ao programa-quadro de gestão unificada e transferir o poder de controle do desenvolvedor para o programa-quadro.
Controle a rotação direta: programa de controle puramente manual do desenvolvedor
Inversão de controlo: controlo do programa-quadro
Para dar um exemplo real, uma pessoa dirige sozinha para o trabalho e seu objetivo é chegar à empresa. Ele dirige sozinho e controla sua própria rota. E se ele entregar o controle da direção e pegar o ônibus, basta escolher o ônibus correspondente para chegar à empresa. Só em termos de controle, as pessoas ficam liberadas. Elas só precisam lembrar qual ônibus pegar. A chance de cometer erros também é reduzida e as pessoas ficam muito mais tranquilas. O sistema de barramento é o controlador e as linhas de barramento são as configurações acordadas.
Através da comparação real acima, acho que deveria ser capaz de compreender a inversão de controle.
2.4 Resumo
Do Koa ao Nest, do front-end JQuery ao Vue React. Na verdade, todos eles são implementados passo a passo através do encapsulamento da estrutura para resolver os problemas de ineficiência da era anterior.
O desenvolvimento de aplicativos Koa acima usa uma maneira muito primitiva de controlar dependências e instanciação, que é semelhante ao domínio operacional JQuery no front end. Essa forma muito primitiva é chamada de encaminhamento de controle, e o Vue React é como o que o Nest fornece. controlador, todos eles podem ser chamados de inversão de controle. Este também é o meu entendimento pessoal. Se houver algum problema, espero que Deus o indique.
Vamos falar sobre o módulo @Module no Nest. A injeção de dependência e a inversão de controle exigem isso como mídia.
Nestjs implementa inversão de controle e concorda em configurar as importações, exportações e provedores do módulo (@module) para gerenciar o provedor, que é a injeção de dependência da classe.
Os provedores podem ser entendidos como registrando e instanciando classes no módulo atual. Os seguintes A e B são instanciados no módulo atual. Se B fizer referência a A no construtor, ele se refere à instância A do MóduloD atual.
importar { Módulo } de '@nestjs/common'; importar {MóduloX} de './moduleX'; importar {A} de './A'; importar {B} de './B'; @Módulo({ importações: [MóduloX], provedores: [A,B], exportações: [A] }) exportar classe MóduloD {} // B classe B{ construtor(a:A){ isto.a = a; } }
exports
referem-se a classes instanciadas em providers
no módulo atual como classes que podem ser compartilhadas por módulos externos. Por exemplo, quando a classe C do ModuleF é instanciada, desejo injetar diretamente a instância da classe A do ModuleD. Basta definir as exportações A no ModuleD e importar o ModuleD por meio imports
no ModuleF.
De acordo com o método de escrita a seguir, o programa de inversão de controle verificará automaticamente as dependências. Primeiro, verifique se existe o provedor A nos provedores do seu próprio módulo. Caso contrário, procure uma instância de A no MóduloD importado. encontrado, obtenha a instância A do ModuleD injetada na instância C.
importar { Módulo } de '@nestjs/common'; importar {MóduloD} de './moduleD'; importar {C} de './C'; @Módulo({ importações: [ModuleD], provedores: [C], }) exportar classe MóduloF {} // C classe C { construtor(a:A){ isto.a = a; } }
Portanto, se você deseja que um módulo externo use a instância de classe do módulo atual, você deve primeiro definir a classe de instanciação nos providers
do módulo atual e, em seguida, definir e exportar esta classe, caso contrário, um erro será relatado.
//Corrigir @Module({ provedores: [A], exportações: [A] }) //Erro @Module({ provedores: [], exportações: [A] })
Olhando para trás, para o processo de localização de instâncias do módulosuplementar posterior
, é realmente um pouco confuso. O ponto principal é que as classes nos provedores serão instanciadas e, após a instanciação, elas se tornarão provedores. Somente as classes nos provedores do módulo serão instanciadas, e a exportação e a importação são apenas configurações de relacionamento organizacional. O módulo dará prioridade ao uso de seu próprio provedor. Caso contrário, verifique se o módulo importado possui um provedor correspondente.
Deixe-me mencionar alguns pontos de conhecimento da
classe de exportação C {.
construtor(privado a: A) { } }
Como o TypeScript suporta parâmetros de construtor (privado, protegido, público, somente leitura) para serem definidos implícita e automaticamente como atributos de classe (propriedade de parâmetro), não há necessidade de usar this.a = a
. É assim que está escrito no Nest.
O conceito de metaprogramação é refletido na estrutura Nest Inversão de controle e decoradores são a implementação da metaprogramação. Pode-se entender aproximadamente que a essência da metaprogramação ainda é a programação, mas existem alguns programas abstratos no meio. Este programa abstrato pode identificar metadados (como dados de objetos em @Module), que na verdade são uma capacidade de expansão que pode usar outros. programas como dados para manipular. Quando escrevemos esses programas abstratos, estamos metaprogramando.
4.1 Metadados
Metadados são frequentemente mencionados em documentos Nest O conceito de metadados pode ser confuso quando você os vê pela primeira vez. Você precisa se acostumar e entendê-los com o passar do tempo, para não precisar se preocupar muito. emaranhado.
A definição de metadados é: dados que descrevem dados, principalmente informações que descrevem atributos de dados, e também podem ser entendidos como dados que descrevem programas.
exports、providers、imports、controllers
configurados por @Module no Nest são todos metadados , porque são dados usados para descrever relacionamentos do programa. Essas informações de dados não são os dados reais exibidos ao usuário final, mas são lidos e reconhecidos pelo. programa-quadro.
4.2 Nest Decorator
Se você observar o código-fonte do decorador no Nest, descobrirá que quase todo decorador define metadados apenas por meio de metadados de reflexão.
Função de exportaçãodo decorador @Injectable
Injectable(opções?: InjectableOptions): ClassDecorator { return (destino: objeto) => { Reflect.defineMetadata(INJECTABLE_WATERMARK, verdadeiro, alvo); Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, opções, alvo); }; }
Existe o conceito de reflexão aqui, e a reflexão é relativamente fácil de entender. Tomemos o decorador @Module como exemplo para definir providers
de metadados. Ele apenas passa classes para o array providers
providers
. ser usado automaticamente pelo programa de estrutura. A instanciação se torna um provedor e os desenvolvedores não precisam executar explicitamente a instanciação e a injeção de dependência. Uma classe só se torna um provedor depois de ser instanciada em um módulo. As classes nos providers
são refletidas e se tornam provedores. A inversão de controle é a tecnologia de reflexão utilizada.
Outro exemplo é o ORM (Mapeamento Relacional de Objetos) no banco de dados. Para usar o ORM, você só precisa definir os campos da tabela, e a biblioteca ORM converterá automaticamente os dados do objeto em instruções SQL.
dados const = TableModel.build(); dados.tempo = 1; dados.browser = 'cromo'; dados.save(); // SQL: INSERT INTO tableName (time,browser) [{"time":1,"browser":"chrome"}]
A biblioteca ORM usa tecnologia de reflexão, para que os usuários só precisem prestar atenção aos próprios dados do campo, e o objeto é a reflexão da biblioteca ORM e se torna uma instrução de execução SQL. Os desenvolvedores só precisam se concentrar nos campos de dados e não precisam escrever SQL.
4.3 reflect-metadata
reflect-metadata é uma biblioteca de reflexão que o Nest usa para gerenciar metadados. reflect-metadata usa WeakMap para criar uma instância única global e define e obtém os metadados do objeto decorado (classe, método, etc.) por meio dos métodos set e get.
// Dê uma olhada var _WeakMap = !usePolyfill && typeof WeakMap === "function" : CreateWeakMapPolyfill(); var Metadados = new _WeakMap(); função defineMetadata(){ OrdinaryDefineOwnMetadata(){ GetOrCreateMetadataMap(){ var targetMetadata = Metadata.get(O); if (IsUndefined(targetMetadata)) { se (!Criar) retornar indefinido; targetMetadata = new _Map(); Metadata.set(O, targetMetadata); } var metadataMap = targetMetadata.get(P); if (IsUndefined(metadataMap)) { se (!Criar) retornar indefinido; metadataMap = new _Map(); targetMetadata.set(P, metadataMap); } retornar metadataMap; } } }
reflect-metadata armazena os metadados da pessoa condecorada no objeto singleton global para gerenciamento unificado. reflect-metadata não implementa reflexão específica, mas fornece uma biblioteca de ferramentas para auxiliar na implementação da reflexão.
, vejamos as questões anteriores.
Por que a inversão de controle é necessária?
O que é injeção de dependência?
O que o decorador faz?
Quais são os princípios de implementação de provedores, importações e exportações em módulos (@Module)?
1 e 2 Acho que já deixei claro antes. Se ainda estiver um pouco vago, sugiro que você volte e leia novamente e consulte alguns outros artigos para ajudá-lo a compreender o conhecimento através do pensamento de diferentes autores.
5.1 Problema [3 4] Visão geral:
Nest usa tecnologia de reflexão para implementar inversão de controle e fornece recursos de metaprogramação. Os desenvolvedores usam o decorador @Module para decorar classes e definir metadados (provedoresimportaçõesexportações), e os metadados são armazenados em um ambiente global. objeto (usando a biblioteca reflect-metadata). Após a execução do programa, o programa de controle dentro da estrutura Nest lê e registra a árvore do módulo, verifica os metadados e instancia a classe para se tornar um provedor e fornece-os em todos os módulos de acordo com a definição de provedoresimportaçõesexportações nos metadados do módulo . Procure instâncias (provedores) de outras classes dependentes da classe atual e injete-as por meio do construtor após encontrá-las.
Este artigo contém muitos conceitos e não fornece uma análise muito detalhada. Os conceitos demoram para serem compreendidos lentamente. Se você não entender bem no momento, não fique muito ansioso. Ok, é isso. Este artigo ainda exigiu muito esforço. Amigos que gostam dele esperam que você possa se conectar três vezes com um clique ~.