Componentes de reação final

O que eu gosto no ecossistema React é que a IDEA está por trás de muitas decisões. Vários autores escrevem vários artigos em apoio à ordem existente e explicam por que tudo está “certo”, para que todos entendam que a festa está no caminho certo.


Depois de algum tempo, a IDEA muda um pouco e tudo começa do começo.


E o começo desta história é a separação de componentes em contêineres e não contêineres (popularmente chamados Dumb Components, desculpe pelo meu francês).



O problema


O problema é muito simples - testes de unidade. Recentemente, houve algum movimento em direção aos testes de integração - bem, você sabe "Escrever testes. Não muitos. Principalmente integração". . Esta não é uma má ideia, e se o tempo for curto (e os testes não forem particularmente necessários) - é isso que você precisa fazer. Vamos chamar de testes de fumaça - para verificar se nada parece explodir.


Se houver muito tempo e forem necessários testes, é melhor não seguir esse caminho, porque escrever bons testes de integração é muito, muito longo. Só porque eles crescerão e crescerão, e para testar o terceiro botão à direita, primeiro você precisará clicar em 3 botões no menu e não se esqueça de fazer login. Em geral - aqui está uma explosão combinatória em uma bandeja de prata.


A solução aqui é uma e simples (por definição) - testes de unidade. A capacidade de iniciar testes com algum estado pronto de alguma parte do aplicativo. Mais precisamente, reduzir (restringir) a área de teste do Aplicativo ou do Big Block para algo pequeno - uma unidade, não importa o que seja. Não é necessário usar enzimas - você pode executar testes no navegador, se a alma perguntar. A coisa mais importante aqui é poder testar algo isoladamente . E sem muita dificuldade.


O isolamento é um dos pontos-chave no teste de unidade, e é por isso que os testes de unidade não gostam. Eles não gostam disso por várias razões:


  • por exemplo, sua "unidade" é removida do aplicativo e não funciona em sua composição, mesmo quando seus próprios testes são verdes.
  • ou, por exemplo, porque o isolamento é um cavalo tão esférico no vácuo que ninguém viu. Como alcançá-lo e como mensurá-lo?

Pessoalmente, não vejo problemas aqui. No primeiro parágrafo, é claro, você pode recomendar testes de integração, eles foram inventados para isso - para verificar como os componentes pré-testados são montados corretamente. Você confia nos pacotes npm que testam, é claro, apenas eles próprios e não eles próprios como parte do seu aplicativo. Como seus "componentes" diferem dos pacotes "não seus"?


Com o segundo parágrafo, tudo é um pouco mais complicado. E este artigo será exatamente sobre esse ponto (e tudo o que era antes - uma introdução) - sobre como tornar uma unidade " unitária " testável .


Dividir e conquistar


A idéia de separar os componentes do React em "Container" e "Presentation" não é nova, bem descrita e já conseguiu ficar um pouco desatualizada. Se tomarmos como base (o que 99% dos desenvolvedores fazem) um artigo de Dan Abramov , o Componente de Apresentação:


  • Preocupam-se com a aparência das coisas
  • Podem conter componentes de apresentação e de contêiner ** internos e geralmente têm sua própria marcação e estilos DOM)
  • Slots de suporte (muitas vezes permitem a contenção por this.props.children)
  • Independente do aplicativo (não possui dependências no restante do aplicativo, como ações ou lojas do Flux)
  • Não dependa de dados (não especifique como os dados são carregados ou alterados)
  • A interface é baseada em adereços (Receba dados e retornos de chamada exclusivamente via adereços)
  • Frequentemente sem estado (raramente têm seu próprio estado (quando o fazem, é o estado da interface do usuário em vez de dados))
  • Frequentemente SFC (são escritos como componentes funcionais, a menos que precisem de estado, ganchos de ciclo de vida ou otimizações de desempenho)

Bem, os contêineres são toda a lógica, todo o acesso aos dados e todo o aplicativo em princípio.


Em um mundo ideal, os contêineres são o tronco e os componentes da apresentação são as folhas.

Existem dois pontos principais na definição de Dan: "Independente de aplicativo" , que é quase uma definição acadêmica de "unidade" e * "Pode conter outros componentes de apresentação e recipientes ** " *, onde essas estrelas são especialmente interessantes.


(tradução gratuita) ** Nas versões anteriores do meu artigo, eu (Dan) disse que os componentes de apresentação deveriam conter apenas outros componentes de apresentação. Acho que não. O tipo de componente é os detalhes e pode mudar com o tempo. Em geral, não compartilhe e tudo ficará bem.

Vamos lembrar o que acontece depois disso:


  • No livro de histórias, tudo cai, porque algum tipo de contêiner, no terceiro botão à esquerda, rasteja para o lado do qual não há nenhum. Saudações especiais para graphql, react-router e outros react-intl.
  • A capacidade de usar mount nos testes é perdida, porque renderiza tudo de A a Z e, novamente, em algum lugar nas profundezas da árvore de renderização, alguém faz alguma coisa e os testes caem.
  • A capacidade de controlar o estado do aplicativo é perdida, pois (figurativamente falando) a capacidade de molhar seletores / resolvedores (especialmente com proxyquire) é perdida e todo o portão precisa estar molhado. E isso é legal para testes de unidade.

Se você acha que os problemas são um pouco exagerados, tente trabalhar em equipe quando esses contêineres, que serão usados ​​nos seus não contêineres, mudarem em outros departamentos e, como resultado, você e eles analisam os testes e não conseguem entender por que ontem tudo funcionou, e agora novamente.

Como resultado, você deve usar o superficial, que por design elimina todos os efeitos colaterais prejudiciais (e inesperados). Aqui está um exemplo simples do artigo "Por que eu sempre uso superficial"


Imagine que a dica de ferramenta renderize "?". Quando clicado, o próprio tipo será mostrado.


 import Tooltip from 'react-cool-tooltip'; const MyComponent = () => { <Tooltip> hint: {veryImportantTextYouHaveToTest} </Tooltip> } 

Como testá-lo? Monte + clique + verifique o que é visível. Este é um teste de integração, não uma unidade, e a questão é como clicar em um componente "externo" para você. Não há problema com o superficial, pois não há cérebro e o próprio "componente alienígena". Mas existem cérebros aqui, já que a dica de ferramenta é um contêiner, enquanto o MyComponent é praticamente uma apresentação.


 jest.mock('react-cool-tooltip', {default: ({children}) => childlren}); 

Mas se você reagir, dica legal, não haverá problemas com o teste. O "componente" tornou-se muito mais estúpido, muito mais curto, muito mais finito .


Componente final


  • um componente com um tamanho conhecido, que pode incluir outros componentes finais anteriormente conhecidos ou que não os contenham.
  • não contém outros contêineres, pois eles contêm um estado não controlado e um tamanho "aumentado", ou seja, tornar o componente atual infinito .
  • caso contrário, é um componente de apresentação regular. De fato, exatamente como descrito na primeira versão do artigo de Dan.

O componente final é apenas uma engrenagem retirada de um grande mecanismo.


A questão toda é como retirá-lo.


Solução 1 - DI


Meu favorito é injeção de dependência. Dan também o ama . Em geral, isso não é DI, mas "slots". Em poucas palavras - não é necessário usar recipientes dentro da apresentação - eles precisam ser injetados lá. E nos testes será possível injetar outra coisa.


 //    mount     const PageChrome = ({children, aside}) => ( <section> <aside>{aside}</aside> {children} </section> ); //     shallow,       //     mount ? , ,   wiring? const PageChromeContainer = () => ( <PageChrome aside={<ASideContainer />}> <Page /> </PageChrome> ); 

É exatamente esse o caso quando "os contêineres são o tronco e os componentes da apresentação são folhas"


Solução 2 - Limites


O DI geralmente pode ser legal. Provavelmente agora% username% pensa em como pode ser aplicado na base de código atual e a solução não foi inventada ...


Nesses casos, o Borders salvará você.


 const Boundary = ({children}) => ( process.env.NODE_ENV === 'test' ? null : children // //  jest.mock ); const PageChrome = () => ( <section> <aside><Boundary><ASideContainer /></Boundary></aside> <Boundary><Page /></Boundary> </section> ); 

Aqui, em vez de "slots", todos os "pontos de transição" simplesmente se transformam em Fronteira, que renderiza qualquer coisa durante os testes. Declarativamente , e exatamente o que você precisa para "tirar o equipamento".


Solução 3 - Camada


As bordas podem ser um pouco difíceis, e pode ser mais fácil torná-las um pouco mais inteligentes adicionando um pouco de conhecimento sobre a Camada.


 const checkTier = tier => tier === currentTier; const withTier = tier => WrapperComponent => (props) => ( (process.env.NODE_ENV !== 'test' || checkTier(tier)) && <WrapperComponent{...props} /> ); const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); const ASideContainer = withTier('UI')(...) const Page = withTier('Page')(...) const PageChromeContainer = withTier('UI')(PageChrome); 

Sob o nome Camada / Camada, pode haver coisas diferentes - recurso, duck, módulo ou apenas essa camada / camada. O ponto não é importante, o principal é que você pode puxar a marcha, talvez não uma, mas o número final, de alguma forma traçando uma linha entre o que você precisa e o que não precisa (para testes diferentes, essa é uma borda diferente).


E nada impede de marcar esses limites de alguma maneira diferente.


Solução 4 - Preocupações separadas


Se a solução (por definição) reside na separação de entidades - o que acontecerá se as tomarmos e separarmos?


Os "contêineres", dos quais tanto não gostamos, são geralmente chamados de contêineres . E se não - nada impede agora de começar a nomear componentes de alguma maneira mais sonora. Ou eles têm um determinado padrão em seu nome - Connect (WrappedComonent) ou GraphQL / Query.


E se, em tempo de execução, traçar uma linha entre entidades com base em um nome?


 const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); // remove all components matching react-redux pattern reactRemock.mock(/Connect\(\w\)/) // all any other container reactRemock.mock(/Container/) 

Além de uma linha nos testes, o reag-remock removerá todos os recipientes que possam interferir nos testes.


Em princípio, essa abordagem pode ser usada para testar os próprios contêineres - você só precisa remover tudo, exceto o primeiro.


 import {createElement, remock} from 'react-remock'; //  "" const ContainerCondition = React.createContext(true); reactRemock.mock(/Connect\(\w\)/, (type, props, children) => ( <ContainerCondition.Consumer> { opened => ( opened ? ( // ""     <ContainerCondition.Provider value={false}> {createElement(type, props, ...children)} <ContainerCondition.Provider> ) // "" : null )} </ContainerCondition.Consumer> ) 

Mais uma vez - algumas linhas e equipamentos removidos.


Total


No ano passado, o teste de componentes do React se tornou mais complicado, especialmente para montagem - você precisa sobrescrever todos os 10 provedores, contextos e está ficando cada vez mais difícil testar o componente certo no estilo certo - há muitas cordas para puxar.
Alguém cospe e entra no mundo raso. Alguém acenou com as mãos em testes de unidade e transferiu tudo para Cypress (ande como ande!).


Outra pessoa cutuca um dedo com a reação, diz que esses são efeitos algébricos e você pode fazer o que quiser. Todos os exemplos acima são essencialmente o uso desses efeitos e zombes algébricos . Para mim e DI estes são moki.


PS: Este post foi escrito como uma resposta aos comentários no React / RFC sobre o fato de que a equipe do React quebrou tudo, e todos os polímeros de lá também
PPS: Este post é na verdade uma tradução muito gratuita de outro
PPPS: em geral, para isolamento real, observe o rewiremock

Source: https://habr.com/ru/post/pt434830/


All Articles