Diretrizes práticas para o desenvolvimento de aplicativos React em larga escala. Parte 2: gerenciamento de estado, roteamento

Hoje publicamos a segunda parte da tradução do material, que é dedicada ao desenvolvimento de aplicações React em larga escala. Aqui, falaremos sobre o gerenciamento do estado dos aplicativos, roteamento e desenvolvimento de interface.



Parte 1: Diretrizes práticas para o desenvolvimento de aplicativos React em larga escala. Planejamento, ações, fontes de dados e APIs

Parte 2: Diretrizes práticas para o desenvolvimento de aplicativos React em larga escala. Parte 2: gerenciamento de estado, roteamento


Gerenciamento de estado de aplicativos, integração Redux, organização de roteamento


Aqui, falaremos sobre como você pode estender a funcionalidade do Redux para poder executar operações complexas no aplicativo. Se esses mecanismos forem mal implementados, eles podem violar o padrão usado no design do repositório.

As funções de gerador de JavaScript podem resolver muitos dos problemas associados à programação assíncrona. O fato é que essas funções podem ser iniciadas e interrompidas a pedido do programador. O middleware Redux-saga usa esse conceito para gerenciar aspectos problemáticos de um aplicativo. Em particular, estamos falando em resolver esses problemas, que não podem ser resolvidos com a ajuda de redutores, apresentados na forma de funções puras.

OlvingResolver tarefas que não podem ser resolvidas com funções puras


Considere o seguinte cenário. Você foi convidado a trabalhar em um aplicativo desenvolvido para uma empresa que trabalha no mercado imobiliário. O cliente deseja obter um site novo e melhor. À sua disposição, existe uma API REST, você tem layouts de todas as páginas preparadas usando o Zapier, delineou um plano de aplicativo. Mas então surgiu um grande problema.

A empresa cliente há muito tempo usa um determinado sistema de gerenciamento de conteúdo (CMS). Os funcionários da empresa estão bem cientes desse sistema; portanto, o cliente não deseja mudar para um novo CMS apenas para facilitar a gravação de novas postagens em um blog corporativo. Além disso, você também precisa copiar as publicações existentes do blog para um novo site, e isso também pode levar a um problema.

O bom é que o CMS usado pelo cliente possui uma API conveniente por meio da qual você pode acessar as publicações do blog. Porém, se você criou um agente para trabalhar com essa API, a situação é complicada pelo fato de ela estar localizada em um determinado servidor no qual os dados são apresentados de maneira alguma conforme necessário.

Este é um exemplo de problema, algo que pode poluir o código do aplicativo, pois aqui você deve incluir nos mecanismos do projeto para trabalhar com a nova API usada para baixar postagens do blog. Você pode lidar com esta situação com redux-saga.
Veja o diagrama a seguir. É assim que nosso aplicativo e API interagem. Fazemos o download de publicações em segundo plano usando redux-saga.


Diagrama de aplicativo usando armazenamento Redux e redux-saga

Aqui, o componente despacha a ação GET.BLOGS . Como o aplicativo usa redux-saga, essa solicitação será interceptada. Depois disso, a função do gerador fará o download dos dados do armazenamento de dados em segundo plano e atualizará o estado do aplicativo suportado pelo Redux.

Aqui está um exemplo de como a função geradora para carregar publicações (tais funções são chamadas de “sagas”) pode parecer na situação descrita. Sagas podem ser usados ​​em outros cenários. Por exemplo, para organizar o armazenamento de dados do usuário (digamos, podem ser tokens), pois esse é outro exemplo de uma tarefa para a qual funções puras não são adequadas.

 ... function* fetchPosts(action) { if (action.type === WP_POSTS.LIST.REQUESTED) {   try {     const response = yield call(wpGet, {       model: WP_POSTS.MODEL,       contentType: APPLICATION_JSON,       query: action.payload.query,     });     if (response.error) {       yield put({         type: WP_POSTS.LIST.FAILED,         payload: response.error.response.data.msg,       });       return;         yield put({       type: WP_POSTS.LIST.SUCCESS,       payload: {         posts: response.data,         total: response.headers['x-wp-total'],         query: action.payload.query,       },       view: action.view,     });   } catch (e) {     yield put({ type: WP_POSTS.LIST.FAILED, payload: e.message });  ... 

A saga apresentada aqui espera ações como WP_POSTS.LIST.REQUESTED . Ao receber essa ação, ele carrega dados da API. Depois de receber os dados, ela envia outra ação - WP_POSTS.LIST.SUCCESS . Seu processamento leva à atualização do repositório usando o redutor apropriado.

▍ Introdução de redutores


Ao desenvolver aplicativos grandes, é impossível planejar com antecedência o dispositivo de todos os modelos necessários. Além disso, à medida que o tamanho da aplicação aumenta, o uso da tecnologia de introdução de redutores ajuda a economizar uma grande quantidade de horas de trabalho. Essa técnica permite que os desenvolvedores adicionem novos redutores ao sistema sem reescrever o repositório inteiro.

Existem bibliotecas projetadas para criar repositórios dinâmicos do Redux. No entanto, prefiro o mecanismo de introdução de redutores, pois ele oferece ao desenvolvedor um certo nível de flexibilidade. Por exemplo, um aplicativo existente pode ser equipado com esse mecanismo sem a necessidade de reorganizar seriamente o aplicativo.

A introdução de redutores é uma forma de separação de código. A comunidade de desenvolvedores do React está adotando entusiasticamente essa tecnologia. Usarei esse trecho de código para demonstrar a aparência e os recursos do mecanismo de implementação dos redutores.

Primeiro, vejamos sua integração com o Redux:

 ... const withConnect = connect( mapStateToProps, mapDispatchToProps, ); const withReducer = injectReducer({ key: BLOG_VIEW, reducer: blogReducer, }); class BlogPage extends React.Component {  ... } export default compose( withReducer, withConnect, )(BlogPage); 

Este código faz parte do arquivo BlogPage.js que contém o componente do aplicativo.

Aqui nós, no comando export, não usamos a função connect , mas a função compose . Essa é uma das funções da biblioteca Redux que permite compor várias funções. A lista de funções passadas para compose deve ser lida da direita para a esquerda ou de baixo para cima.

Você pode aprender com a documentação do Redux que a função de compose permite criar transformações de funções profundamente aninhadas. Nesse caso, o programador fica livre da necessidade de usar estruturas muito longas. Essas construções se parecem com linhas de código que representam chamadas para algumas funções, passando-lhes os resultados de chamadas para outras funções como argumentos. A documentação observa que a função de compose deve ser usada com cautela.

A função mais à direita em uma composição pode receber muitos argumentos, mas apenas um argumento pode ser passado para as funções a seguir. Como resultado, chamando a função que resultou do uso de compose , passamos a ela o que é necessário das funções originais, que estão à direita de todas as outras. Por isso, passamos a função de composição para a função withConnect como o último parâmetro. Como resultado, a função de compose pode ser usada como a função de connect .

▍ Roteamento e Redux


Existem várias ferramentas usadas para resolver problemas de roteamento em aplicativos. Nesta seção, no entanto, focaremos a biblioteca react-router-dom . Expandiremos seus recursos para que ele possa trabalhar com o Redux.

Na maioria das vezes, o roteador React é usado assim: o componente raiz é BrowserRouter tag BrowserRouter e os contêineres filhos são withRouter() método withRouter() e exportados ( aqui está um exemplo).

Com essa abordagem, o componente filho recebe, por meio do mecanismo de props , um objeto de history contém algumas propriedades específicas da sessão do usuário atual. Existem alguns métodos neste objeto que podem ser usados ​​para controlar a navegação.

Essa opção de roteamento pode causar problemas em aplicativos grandes. Isso se deve ao fato de eles não terem um objeto history centralizado. Além disso, os componentes que não são renderizados com <Route> não podem funcionar com o objeto de history . Aqui está um exemplo usando <Route> :

 <Route path="/" exact component={HomePage} /> 

Para resolver esse problema, usaremos a biblioteca de roteador de reação conectada , que nos permitirá estabelecer o roteamento usando o método de dispatch . A integração dessa biblioteca no projeto exigirá algumas modificações. Em particular, será necessário criar um novo redutor projetado especificamente para rotas (isso é bastante óbvio) e também adicionar alguns mecanismos auxiliares ao sistema.

Após a conclusão de sua configuração, o novo sistema de roteamento pode ser usado através do Redux. Portanto, a navegação no aplicativo pode ser implementada enviando ações.

Para tirar proveito dos recursos da biblioteca de roteador de reação conectado no componente, simplesmente mapeamos o método de dispatch para o repositório, fazendo isso de acordo com as necessidades do aplicativo. Aqui está um exemplo de código que demonstra o uso da biblioteca de roteador de reação conectado (para que esse código funcione, você precisa que o restante do sistema esteja configurado para usar o roteador de reação-conectado).

 import { push } from 'connected-react-router' ... const mapDispatchToProps = dispatch => ({  goTo: payload => {    dispatch(push(payload.path));  }, }); class DemoComponent extends React.Component {  render() {    return (      <Child        onClick={          () => {            this.props.goTo({ path: `/gallery/`});                      />    } ... 

Aqui, o método goTo despacha uma ação que envia a URL necessária goTo pilha de histórico goTo navegador. Anteriormente, o método goTo era goTo para o repositório. Portanto, esse método é passado para o DemoComponent no objeto props .

Interface de usuário dinâmica e necessidades de um aplicativo em crescimento


Com o tempo, apesar da presença de um back-end adequado para o aplicativo e de uma parte do cliente de alta qualidade, alguns elementos da interface do usuário começam a ter um efeito ruim sobre os usuários. Isso se deve à implementação irracional dos componentes, que, à primeira vista, parece muito simples. Nesta seção, discutiremos recomendações para a criação de alguns widgets. Sua implementação correta, conforme o aplicativo cresce, fica mais complicada.

Loading Carregamento lento e React.Suspense


A melhor parte da natureza assíncrona do JavaScript é que ele tira proveito de todo o potencial do navegador. Talvez o verdadeiro bem seja que, para iniciar um determinado processo, você não precise esperar pela conclusão da tarefa anterior. No entanto, os desenvolvedores não podem influenciar a rede e a velocidade com que os vários materiais necessários para o funcionamento dos sites são carregados.

Os subsistemas de rede são geralmente considerados não confiáveis ​​e propensos a erros.

O desenvolvedor, em um esforço para tornar sua aplicação o mais alta qualidade possível, pode sujeitá-la a muitas verificações e obter sua aprovação com êxito. Mas ainda existem algumas coisas, como o status da conexão de rede ou o tempo de resposta do servidor, que o desenvolvedor não pode influenciar.

Mas os criadores do software não procuram justificar o trabalho de baixa qualidade dos aplicativos com frases como "esse não é o meu negócio". Eles descobriram maneiras interessantes de lidar com problemas de rede.

Em algumas partes do aplicativo front-end, pode ser necessário mostrar alguns materiais de backup (como carregar muito mais rápido que os materiais reais). Isso evitará que o usuário contemple a "contração" das páginas de carregamento ou, pior ainda, sobre esses ícones.


É melhor que os usuários não vejam algo assim.

A tecnologia React Suspense permite que você lide com esses problemas. Por exemplo, permite exibir um determinado indicador durante o carregamento de dados. Embora isso também possa ser feito manualmente, definindo a propriedade isLoaded como true , o uso da API Suspense torna o código muito mais limpo.

Aqui você pode assistir a um bom vídeo sobre o Suspense, no qual Jared Palmer apresenta o público a essa tecnologia e mostra alguns de seus recursos usando um exemplo de aplicativo real .

Veja como o aplicativo funciona sem usar o Suspense.


Aplicativo no qual o Suspense não é usado

Equipar um componente com suporte ao Suspense é muito mais fácil do que usar o isLoaded todo o isLoaded . Vamos começar colocando o contêiner de App pai no React.StrictMode . Garantimos que, entre os módulos React usados ​​no aplicativo, não haja aqueles que sejam considerados obsoletos.

 <React.Suspense fallback={<Spinner size="large" />}>  <ArtistDetails id={this.props.id}/>  <ArtistTopTracks />  <ArtistAlbums id={this.props.id}/> </React.Suspense> 

Os componentes envolvidos nas tags React.Suspense , durante o carregamento do conteúdo principal, carregam e exibem o que é especificado na propriedade fallback . Devemos nos esforçar para garantir que os componentes usados ​​na propriedade fallback tenham o menor volume possível e sejam organizados da maneira mais simples possível.


Aplicativo que usa Suspense

Components Componentes adaptáveis


Em aplicativos front-end grandes, a manifestação de padrões repetidos é comum. Ao mesmo tempo, no início do trabalho, isso pode ser quase completamente óbvio. Não há nada a ser feito sobre isso, mas você deve ter se deparado com isso.

Por exemplo, existem dois modelos no aplicativo. Um deles é destinado à descrição de pistas de corrida e o segundo - à descrição de carros. A página da lista de carros usa elementos quadrados. Cada um deles contém uma imagem e uma breve descrição.

A lista de rastreamento usa elementos semelhantes. Sua principal característica é que, além da imagem e descrição da pista, eles também têm um pequeno campo indicando se é possível para os espectadores de uma corrida nessa pista comprar algo para comer.


Elemento para a descrição do carro e elemento para a descrição da pista

Esses dois componentes são ligeiramente diferentes um do outro em termos de estilo (eles têm cores de fundo diferentes). O componente que descreve a rota contém algumas informações adicionais sobre o objeto do mundo real que descreve, enquanto o componente que simboliza o carro não possui essas informações. Este exemplo mostra apenas dois modelos. Em uma aplicação grande, muitos modelos semelhantes podem ser digitados, diferindo apenas em pequenas coisas.

A criação de componentes independentes separados para cada uma dessas entidades é contrária ao senso comum.

Um programador pode economizar a necessidade de escrever fragmentos de código que se repetem quase completamente. Isso pode ser feito através do desenvolvimento de componentes adaptativos. No decorrer do trabalho, eles levam em consideração o ambiente em que foram carregados. Considere a barra de pesquisa de um determinado aplicativo.


Barra de pesquisa

Será usado em muitas páginas. Ao mesmo tempo, pequenas alterações serão feitas na aparência e na ordem do trabalho em páginas diferentes. Por exemplo, na página inicial do projeto, ele será um pouco maior do que em outras páginas. Para resolver esse problema, você pode criar um único componente que será exibido de acordo com as propriedades transferidas para ele.

 static propTypes = {  open: PropTypes.bool.isRequired,  setOpen: PropTypes.func.isRequired,  goTo: PropTypes.func.isRequired, }; 

Usando essa técnica, você pode controlar o uso de classes HTML ao renderizar esses elementos, o que permite influenciar a aparência deles.

Outra situação interessante na qual componentes adaptativos podem encontrar aplicação é o mecanismo para dividir certos materiais em páginas. Uma barra de navegação pode estar presente em todas as páginas do aplicativo. As instâncias deste painel em cada uma das páginas serão quase exatamente as mesmas das outras páginas.


Painel de paginação

Suponha que um determinado aplicativo precise de um painel semelhante. Ao trabalhar neste aplicativo, os desenvolvedores cumprem os requisitos articulados oportunos. Em tal situação, o componente adaptativo usado para dividir o material em páginas precisa apenas passar algumas propriedades. Este é o URL e o número de elementos por página.

Sumário


O ecossistema React se tornou tão maduro hoje em dia que é improvável que alguém precise “inventar uma bicicleta” em qualquer estágio do desenvolvimento de aplicativos. Embora isso esteja nas mãos dos desenvolvedores, leva ao fato de que fica difícil escolher exatamente o que funciona bem para cada projeto específico.

Cada projeto é único em termos de escopo e funcionalidade. Não há uma abordagem única ou regra universal no desenvolvimento de aplicativos React. Portanto, antes de iniciar o desenvolvimento, é importante planejar corretamente.

Ao planejar, é muito fácil entender quais ferramentas são criadas diretamente para o projeto e quais claramente não são adequadas para ele, sendo muito grandes para ele. Por exemplo, um aplicativo que consiste em 2 a 3 páginas e executa muito poucas solicitações para determinadas APIs não precisa de repositórios de dados semelhantes aos de que falamos. Estou pronto para ir ainda mais longe nessas considerações e dizer que em pequenos projetos você não precisa usar o Redux.
No estágio de planejamento do aplicativo, ao desenhar layouts de suas páginas, é fácil ver que muitos componentes semelhantes são usados ​​nessas páginas. Se você tentar reutilizar o código desses componentes ou se esforçar para escrever componentes universais, isso ajudará a economizar muito tempo e esforço.

E, finalmente, gostaria de observar que os dados são o núcleo de qualquer aplicativo. E os aplicativos React não são exceção. À medida que a escala do aplicativo aumenta, os volumes de dados processados ​​aumentam, mecanismos de software adicionais para trabalhar com eles aparecem. Algo assim, se o aplicativo foi mal projetado, pode facilmente "esmagar" os programadores, preenchendo-os com tarefas complexas e confusas. Se, no decorrer do planejamento, as questões do uso de data warehouses fossem decididas com antecedência, se a ordem do trabalho das ações, redutores e quedas fosse pensada com antecedência, o trabalho no aplicativo seria muito mais fácil.

Caros leitores! Se você conhece alguma biblioteca ou metodologia de desenvolvimento com bom desempenho ao criar aplicativos React em larga escala, compartilhe-os.

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


All Articles