Então, você decidiu fazer um novo projeto. E este projeto é uma aplicação web. Quanto tempo leva para criar um protótipo básico? Quão difícil é isso? O que um site moderno deve fazer desde o início?
Neste artigo, tentaremos descrever o padrão de um aplicativo da web simples com a seguinte arquitetura:
O que abordaremos:
- configurando o ambiente de desenvolvimento no docker-compose.
- criação de back-end no Flask.
- criando um frontend no Express.
- Crie JS usando o Webpack.
- Reagir, Redux e renderização no servidor.
- filas de tarefas com o RQ.
1. Introdução
Antes do desenvolvimento, é claro, você primeiro precisa decidir o que estamos desenvolvendo! Como um aplicativo modelo para este artigo, decidi criar um mecanismo wiki primitivo. Teremos cartões emitidos no Markdown; eles podem ser assistidos e (em algum momento no futuro) oferecer edições. Tudo isso será organizado como um aplicativo de uma página com renderização no servidor (o que é absolutamente necessário para indexar nossos futuros terabytes de conteúdo).
Vamos dar uma olhada um pouco mais detalhada nos componentes que precisamos para isso:
- Cliente Criaremos um aplicativo de uma página (ou seja, com transições de página usando AJAX) no pacote React + Redux , o que é muito comum no mundo do front-end.
- Frontend . Vamos criar um servidor Express simples que renderize nosso aplicativo React (solicitando todos os dados necessários no back-end de forma assíncrona) e os emita ao usuário.
- Backend . Mestre em lógica de negócios, nosso back-end será um pequeno aplicativo Flask. Armazenaremos dados (nossos cartões) no popular repositório de documentos do MongoDB e, para a fila de tarefas e, possivelmente, no futuro, armazenamento em cache, usaremos o Redis .
- TRABALHADOR . Um contêiner separado para tarefas pesadas será lançado pela biblioteca RQ .
Infraestrutura: git
Provavelmente, não poderíamos falar sobre isso, mas, é claro, conduziremos o desenvolvimento no repositório git.
git init git remote add origin git@github.com:Saluev/habr-app-demo.git git commit --allow-empty -m "Initial commit" git push
(Aqui você deve preencher imediatamente
.gitignore
.)
O rascunho final pode ser visualizado
no Github . Cada seção do artigo corresponde a um commit (eu me envolvi muito para conseguir isso!).
Infraestrutura: docker-compose
Vamos começar configurando o ambiente. Com a abundância de componentes que temos, uma solução de desenvolvimento muito lógica seria usar o docker-compose.
Inclua o arquivo
docker-compose.yml
no repositório
docker-compose.yml
seguinte conteúdo:
version: '3' services: mongo: image: "mongo:latest" redis: image: "redis:alpine" backend: build: context: . dockerfile: ./docker/backend/Dockerfile environment: - APP_ENV=dev depends_on: - mongo - redis ports: - "40001:40001" volumes: - .:/code frontend: build: context: . dockerfile: ./docker/frontend/Dockerfile environment: - APP_ENV=dev - APP_BACKEND_URL=backend:40001 - APP_FRONTEND_PORT=40002 depends_on: - backend ports: - "40002:40002" volumes: - ./frontend:/app/src worker: build: context: . dockerfile: ./docker/worker/Dockerfile environment: - APP_ENV=dev depends_on: - mongo - redis volumes: - .:/code
Vamos dar uma rápida olhada no que está acontecendo aqui.
- Um contêiner MongoDB e um contêiner Redis são criados.
- Um contêiner para o nosso back-end é criado (que descrevemos abaixo). A variável de ambiente APP_ENV = dev é passada a ele (veremos para entender quais configurações do Flask carregar) e sua porta 40001 abrirá fora (por meio dele, nosso cliente do navegador irá para a API).
- Um contêiner do nosso front-end é criado. Uma variedade de variáveis de ambiente também é lançada, o que será útil para nós mais tarde, e a porta 40002 é aberta. Essa é a principal porta do nosso aplicativo da Web: no navegador, iremos para http: // localhost: 40002 .
- O contêiner do nosso trabalhador é criado. Ele não precisa de portas externas e apenas o acesso é necessário no MongoDB e no Redis.
Agora vamos criar arquivos docker. No momento, uma
série de traduções de excelentes artigos sobre o Docker está chegando em Habré - você pode ir com segurança para todos os detalhes.
Vamos começar com o back-end.
# docker/backend/Dockerfile FROM python:stretch COPY requirements.txt /tmp/ RUN pip install -r /tmp/requirements.txt ADD . /code WORKDIR /code CMD gunicorn -w 1 -b 0.0.0.0:40001 --worker-class gevent backend.server:app
Entende-se que executamos o aplicativo gunicorn Flask, oculto sob o nome do
app
no módulo
backend.server
.
Não menos importante
docker/backend/.dockerignore
:
.git .idea .logs .pytest_cache frontend tests venv *.pyc *.pyo
O trabalhador geralmente é semelhante ao back-end, mas em vez de gunicorn temos o lançamento habitual de um módulo de poço:
# docker/worker/Dockerfile FROM python:stretch COPY requirements.txt /tmp/ RUN pip install -r /tmp/requirements.txt ADD . /code WORKDIR /code CMD python -m worker
Faremos todo o trabalho em
worker/__main__.py
.
.dockerignore
trabalhador
.dockerignore
é completamente semelhante ao backend
.dockerignore
.
Finalmente, o frontend. Há um
artigo inteiro sobre ele sobre Habré, mas, a julgar pela
extensa discussão sobre StackOverflow e comentários no espírito de "Gente, já é 2018, ainda não há solução normal?" tudo não é tão simples lá. Eu decidi nesta versão do arquivo docker.
# docker/frontend/Dockerfile FROM node:carbon WORKDIR /app # package.json package-lock.json npm install, . COPY frontend/package*.json ./ RUN npm install # , # PATH. ENV PATH /app/node_modules/.bin:$PATH # . ADD frontend /app/src WORKDIR /app/src RUN npm run build CMD npm run start
Prós:
- tudo é armazenado em cache conforme o esperado (na camada inferior - dependências, na parte superior - a construção do nosso aplicativo);
docker-compose exec frontend npm install --save newDependency
funciona como deveria e modifica o package.json
em nosso repositório (o que não seria o caso se docker-compose exec frontend npm install --save newDependency
COPY, como muitas pessoas sugerem). Não seria desejável executar o npm install --save newDependency
fora do contêiner de qualquer maneira, porque algumas dependências do novo pacote já podem estar presentes e ser construídas sob uma plataforma diferente (na que está dentro da janela de encaixe e não no nosso macbook de trabalho, por exemplo ) e, no entanto, geralmente não queremos exigir a presença de Node na máquina de desenvolvimento. Um Docker para governar todos eles!
Bem, é claro,
docker/frontend/.dockerignore
:
.git .idea .logs .pytest_cache backend worker tools node_modules npm-debug tests venv
Portanto, nosso quadro de contêiner está pronto e você pode preenchê-lo com conteúdo!
Back-end: estrutura do balão
Adicione
flask
,
flask-cors
gevent
,
gevent
e
gunicorn
a
requirements.txt
e crie um aplicativo Flask simples em
backend/server.py
.
Dissemos ao Flask para extrair as configurações do arquivo
backend.{env}_settings
-
backend.{env}_settings
, o que significa que também precisaremos criar um (pelo menos vazio) arquivo
backend/dev_settings.py
para que tudo decole.
Agora podemos oficialmente aumentar o nosso back-end!
habr-app-demo$ docker-compose up backend ... backend_1 | [2019-02-23 10:09:03 +0000] [6] [INFO] Starting gunicorn 19.9.0 backend_1 | [2019-02-23 10:09:03 +0000] [6] [INFO] Listening at: http://0.0.0.0:40001 (6) backend_1 | [2019-02-23 10:09:03 +0000] [6] [INFO] Using worker: gevent backend_1 | [2019-02-23 10:09:03 +0000] [9] [INFO] Booting worker with pid: 9
Nós seguimos em frente.
Front-end: estrutura Express
Vamos começar criando um pacote. Depois de criar a pasta frontend e executar o
npm init
nela, depois de algumas perguntas pouco sofisticadas, obtemos o package.json pronto.
{ "name": "habr-app-demo", "version": "0.0.1", "description": "This is an app demo for Habr article.", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/Saluev/habr-app-demo.git" }, "author": "Tigran Saluev <tigran@saluev.com>", "license": "MIT", "bugs": { "url": "https://github.com/Saluev/habr-app-demo/issues" }, "homepage": "https://github.com/Saluev/habr-app-demo#readme" }
No futuro, não precisamos do Node.js na máquina do desenvolvedor (embora ainda possamos esquivar e iniciar o
npm init
por meio do Docker, tudo bem).
No
Dockerfile
mencionamos o
npm run build
e o
npm run start
- você precisa adicionar os comandos apropriados ao
package.json
:
O comando
build
ainda não faz nada, mas ainda será útil para nós.
Adicione dependências do
Express e crie um aplicativo simples no
index.js
:
Agora o
docker-compose up frontend
aumenta o nosso front-end! Além disso, em
http: // localhost: 40002 , o clássico "Olá, mundo" já deve ser exibido.
Front-end: construa com o webpack e o aplicativo React
É hora de retratar algo mais que texto simples em nosso aplicativo. Nesta seção, adicionaremos o componente React mais simples do
App
e configuraremos a montagem.
Ao programar no React, é muito conveniente usar
JSX , um dialeto JavaScript estendido por construções sintáticas do formulário
render() { return <MyButton color="blue">{this.props.caption}</MyButton>; }
No entanto, os mecanismos JavaScript não o entendem; geralmente, a fase de construção é adicionada ao frontend. Compiladores JavaScript especiais (sim-sim) transformam açúcar sintático em JavaScript clássico
feio , manipulam importações, minimizam e assim por diante.
Ano de 2014. java de pesquisa do apt-cachePortanto, o componente React mais simples parece muito simples.
Ele simplesmente exibirá nossa saudação com um alfinete mais convincente.
Adicione o arquivo
frontend/src/template.js
contém a estrutura HTML mínima de nosso aplicativo futuro:
Adicione um ponto de entrada do cliente:
Para construir toda essa beleza, precisamos:
webpack é um
construtor de moda para JS (embora eu não tenha lido artigos no frontend por três horas, por isso não tenho certeza sobre a moda);
O babel é um compilador para todos os tipos de loções, como JSX, e ao mesmo tempo um provedor de polyfill para todos os casos do IE.
Se a iteração anterior do front-end ainda estiver em execução, tudo o que você precisa fazer é
docker-compose exec frontend npm install --save \ react \ react-dom docker-compose exec frontend npm install --save-dev \ webpack \ webpack-cli \ babel-loader \ @babel/core \ @babel/polyfill \ @babel/preset-env \ @babel/preset-react
para instalar novas dependências. Agora configure o webpack:
Para fazer o babel funcionar, você precisa configurar o
frontend/.babelrc
:
{ "presets": ["@babel/env", "@babel/react"] }
Por fim, faça nosso comando
npm run build
significativo:
// frontend/package.json ... "scripts": { "build": "webpack", "start": "node /app/server.js", "test": "echo \"Error: no test specified\" && exit 1" }, ...
Agora, nosso cliente, junto com um pacote de polyfills e todas as suas dependências, executa babel, compila e dobra em um arquivo minificado monolítico
../dist/client.js
. Adicione a capacidade de enviá-lo como um arquivo estático para nosso aplicativo Express e, na rota padrão, começaremos a retornar nosso HTML:
Sucesso! Agora, se executarmos o
docker-compose up --build frontend
, veremos "Olá, mundo!" em um novo invólucro brilhante e se você tiver a extensão React Developer Tools instalada (
Chrome ,
Firefox ), também haverá uma árvore de componentes React nas ferramentas do desenvolvedor:

Back-end: dados no MongoDB
Antes de seguir adiante e dar vida à nossa aplicação, você deve primeiro respirar no back-end. Parece que estávamos indo para armazenar os cartões marcados em Markdown - é hora de fazê-lo.
Embora
existam ORMs para MongoDB em python , considero o uso de ORMs cruéis e deixo o estudo das soluções apropriadas para você. Em vez disso, faremos uma aula simples para o cartão e o
DAO que acompanha:
(Se você ainda não usa anotações de tipo no Python, verifique
estes artigos !)
Agora vamos criar uma implementação da interface
CardDAO
que recebe
pymongo
um objeto
Database
do
pymongo
(sim, é hora de adicionar o
pymongo
ao
requirements.txt
):
Hora de registrar a configuração do Monga nas configurações de back-end. Simplesmente
MONGO_HOST = "mongo"
nosso contêiner com mongo
mongo
, então
MONGO_HOST = "mongo"
:
Agora precisamos criar o
MongoCardDAO
e dar acesso ao aplicativo Flask. Embora agora tenhamos uma hierarquia muito simples de objetos (configurações → cliente pymongo → banco de dados pymongo →
MongoCardDAO
), vamos criar imediatamente um componente rei centralizado que faz a
injeção de dependência (será útil novamente quando fizermos o trabalhador e as ferramentas).
Hora de adicionar uma nova rota ao aplicativo Flask e apreciar a vista!
Reinicie com o
docker-compose up --build backend
:

Opa ... exatamente. Precisamos adicionar conteúdo! Abriremos a pasta tools e adicionaremos um script que adiciona um cartão de teste:
O
docker-compose exec backend python -m tools.add_test_content
preencherá nossa monga com conteúdo de dentro do contêiner de
docker-compose exec backend python -m tools.add_test_content
-
docker-compose exec backend python -m tools.add_test_content
.

Sucesso! Agora é a hora de apoiar isso no front-end.
Frontend: Redux
Agora queremos criar a rota
/card/:id_or_slug
, pela qual nosso aplicativo React será aberto, carregar os dados do cartão da API e mostrá-los de alguma forma. E aqui, talvez, comece a parte mais difícil, porque queremos que o servidor nos forneça imediatamente HTML com o conteúdo do cartão, adequado para indexação, mas ao mesmo tempo, quando o aplicativo navega entre os cartões, ele recebe todos os dados na forma de JSON da API e a página não sobrecarrega. E para que tudo isso - sem copiar e colar!
Vamos começar adicionando Redux. Redux é uma biblioteca JavaScript para armazenar estado. A idéia é que, em vez dos mil estados implícitos de que seus componentes mudam durante as ações do usuário e outros eventos interessantes, eles têm um estado centralizado e fazem qualquer alteração através de um mecanismo centralizado de ações. Portanto, se antes para a navegação ativamos o GIF de carregamento pela primeira vez, fizemos uma solicitação pelo AJAX e, finalmente, no retorno de chamada bem-sucedido, atualizamos as partes necessárias da página e, no paradigma Redux, somos convidados a enviar a ação "alterar o conteúdo para um gif com animação", que alterará o estado global para que um de seus componentes jogue fora o conteúdo anterior e coloque a animação, faça uma solicitação e envie outra ação em seu retorno de sucesso: "altere o conteúdo para carregado". Em geral, agora vamos ver por nós mesmos.
Vamos começar instalando novas dependências em nosso contêiner.
docker-compose exec frontend npm install --save \ redux \ react-redux \ redux-thunk \ redux-devtools-extension
O primeiro é, de fato, o Redux, o segundo é uma biblioteca especial para cruzar o React e o Redux (escrito por especialistas em acasalamento), o terceiro é uma coisa muito necessária, cuja necessidade é bem justificada em
seu README e, finalmente, o quarto é a biblioteca necessária para o
Redux DevTools funcionar
Extensão .
Vamos começar com o código Redux padrão: criando um redutor que não faz nada e inicializando o estado.
Nosso cliente muda um pouco, preparando-se mentalmente para trabalhar com o Redux:
Agora podemos executar o docker-compose up --build frontend para garantir que nada esteja quebrado, e nosso estado primitivo apareceu no Redux DevTools:

Front-end: página do cartão
Antes de criar páginas com SSR, você precisa criar páginas sem SSR! Finalmente vamos usar nossa API engenhosa para acessar cartões e compor a página do cartão no front-end.
Hora de tirar proveito da inteligência e redesenhar nossa estrutura de estado. Há
muitos materiais sobre esse assunto, então sugiro não abusar da inteligência e focar no simples. Por exemplo, como:
{ "page": { "type": "card", // // type=card: "cardSlug": "...", // "isFetching": false, // API "cardData": {...}, // ( ) // ... }, // ... }
Vamos pegar o componente "card", que usa o conteúdo de cardData como suporte (na verdade, é o conteúdo do nosso card no mongo):
Agora vamos obter um componente para a página inteira com o cartão. Ele será responsável por obter os dados necessários da API e transferi-los para o Card. E faremos a busca de dados da maneira React-Redux.
Primeiro, crie o arquivo
frontend/src/redux/actions.js
e crie uma ação que extraia o conteúdo do cartão da API, se ainda não:
export function fetchCardIfNeeded() { return (dispatch, getState) => { let state = getState().page; if (state.cardData === undefined || state.cardData.slug !== state.cardSlug) { return dispatch(fetchCard()); } }; }
A ação
fetchCard
, que realmente torna a busca, um pouco mais complicada:
function fetchCard() { return (dispatch, getState) => {
Oh, nós temos uma ação que ALGO FAZ! Isso deve ser suportado no redutor:
(Preste atenção à sintaxe da moda para clonar um objeto com a alteração de campos individuais.)Agora que toda a lógica é realizada nas ações do Redux, o próprio componente CardPage
parecerá relativamente simples:
Adicione um processamento simples page.type ao nosso componente raiz do aplicativo:
E agora o último momento permanece - você precisa inicializar de alguma forma page.type
e page.cardSlug
dependendo do URL da página.Mas ainda existem muitas seções neste artigo, mas não podemos criar uma solução de alta qualidade no momento. Vamos fazer isso estúpido por enquanto. Isso é completamente estúpido. Por exemplo, um regular ao inicializar o aplicativo!
docker-compose up --build frontend
,
helloworld
…

, … ? , Markdown!
: RQ
Markdown HTML — «» , , , — .
; Redis
RQ (Redis Queue),
pickle .
, !
Um pouco de código padrão para o trabalhador.
Para a análise em si, conecte a biblioteca mistune e escreva uma função simples:
Logicamente: precisamos CardDAO
obter o código fonte do cartão e salvar o resultado. Mas o objeto que contém a conexão com o armazenamento externo não pode ser serializado via pickle - o que significa que esta tarefa não pode ser imediatamente tomada e colocada em fila para o RQ. De uma maneira boa, precisamos criar Wiring
um trabalhador de lado e jogá-lo de todos os tipos ... Vamos fazê-lo:
Declaramos nossa classe de trabalhos, lançando a fiação como um argumento adicional em todos os problemas. (Observe que ele cria uma nova fiação toda vez, porque alguns clientes não podem ser criados antes da bifurcação que ocorre dentro do RQ antes que a tarefa comece a processar.) Para que todas as nossas tarefas não dependam da fiação - ou seja, de TODOS os nossos objetos - vamos Vamos criar um decorador que obtenha apenas o necessário da fiação:
Adicione um decorador à nossa tarefa e aproveite a vida: import mistune from backend.storage.card import CardDAO from backend.tasks.task import task @task def parse_card_markup(card_dao: CardDAO, card_id: str): card = card_dao.get_by_id(card_id) card.html = _parse_markdown(card.markdown) card_dao.update(card) _parse_markdown = mistune.Markdown(escape=True, hard_wrap=False)
Aproveite a vida? Ugh, eu queria dizer, começamos o trabalhador: $ docker-compose up worker ... Creating habr-app-demo_worker_1 ... done Attaching to habr-app-demo_worker_1 worker_1 | 17:21:03 RQ worker 'rq:worker:49a25686acc34cdfa322feb88a780f00' started, version 0.13.0 worker_1 | 17:21:03 *** Listening on tasks... worker_1 | 17:21:03 Cleaning registries for queue: tasks
III ... ele não faz nada! Claro, porque não definimos uma única tarefa!Vamos reescrever nossa ferramenta, que cria um cartão de teste, para que: a) não caia se o cartão já estiver criado (como no nosso caso); b) colocar a tarefa na análise de marqdown.
Agora, as ferramentas podem ser executadas não apenas no back-end, mas também no trabalhador. Em princípio, agora não nos importamos. Nós docker-compose exec worker python -m tools.add_test_content
o lançamos e, em uma aba vizinha do terminal, vemos um milagre - o trabalhador fez ALGO! worker_1 | 17:34:26 tasks: backend.tasks.parse.parse_card_markup(card_id='5c715dd1e201ce000c6a89fa') (613b53b1-726b-47a4-9c7b-97cad26da1a5) worker_1 | 17:34:27 tasks: Job OK (613b53b1-726b-47a4-9c7b-97cad26da1a5) worker_1 | 17:34:27 Result is kept for 500 seconds
Depois de reconstruir o contêiner com o back-end, finalmente podemos ver o conteúdo do nosso cartão no navegador:
Navegação Frontend
Antes de avançarmos para o SSR, precisamos fazer com que todo o nosso barulho do React seja um pouco significativo e tornar nosso aplicativo de página única verdadeiramente uma única página. Vamos atualizar nossa ferramenta para criar dois (NÃO UM, E DOIS! MAMÃ, EU AGORA GRANDE DESENVOLVEDOR DE DATA!) Cartões que se vinculam e, em seguida, trataremos da navegação entre eles.Agora podemos seguir os links e contemplar como cada vez que nosso maravilhoso aplicativo é reiniciado. Pare com isso!Primeiro, coloque seu manipulador nos cliques nos links. Como o HTML com links vem do back-end e temos o aplicativo React, precisamos de um pouco de foco específico no React.
Como toda a lógica de carregar os cartões em nosso componente CardPage
, na própria ação (incrível!), Nenhuma ação precisa ser tomada: export function navigate(link) { return { type: NAVIGATE, path: link.pathname } }
Adicione um redutor bobo para este caso:
,
CardPage
componentDidUpdate
,
componentWillMount
.
CardPage
(,
cardSlug
) (
componentWillMount
).
,
docker-compose up --build frontend
!

, URL — Hello, world- demo-. , - . history, !
, —
navigate
history.pushState
.
export function navigate(link) { history.pushState(null, "", link.href); return { type: NAVIGATE, path: link.pathname } }
Agora, ao clicar nos links, o URL na barra de endereços do navegador realmente mudará. No entanto, o botão Voltar irá quebrar !Para fazê-lo funcionar, precisamos ouvir o evento do popstate
objeto window
. Além disso, se neste caso queremos fazer a navegação tanto para trás quanto para frente (ou seja, através dispatch(navigate(...))
), precisamos navigate
adicionar um sinalizador especial de “não fazer pushState
” à função (caso contrário, tudo quebrará ainda mais!). Além disso, para distinguir entre "nossos" estados, devemos usar a capacidade pushState
de salvar metadados. Há muita mágica e depuração, então vamos direto ao código! Aqui está a aparência do aplicativo:
E aqui está a ação de navegação:
Agora a história vai funcionar.Bem, o último toque: como agora temos uma ação navigate
, por que não desistimos do código extra no cliente que calcula o estado inicial? Podemos simplesmente ligar para navegar para o local atual:
Copiar e colar destruído!Front-end: renderização do lado do servidor
É hora de nossos principais chips (na minha opinião) - compatibilidade com SEO. Para que os mecanismos de pesquisa possam indexar nosso conteúdo, que é completamente criado dinamicamente nos componentes React, precisamos ser capazes de fornecer o resultado da renderização do React e também aprender como tornar esse resultado interativo novamente.O esquema geral é simples. Primeiro: precisamos inserir o HTML gerado pelo nosso componente React no nosso modelo HTML App
. Este HTML será visto pelos mecanismos de pesquisa (e navegadores com o JS desativado, hehe). Segundo: você precisa adicionar uma tag ao modelo <script>
que salve em algum lugar (por exemplo, um objeto window
) um despejo de estado do qual esse HTML foi renderizado. Em seguida, podemos inicializar imediatamente nosso aplicativo no lado do cliente com esse estado e mostrar o que é necessário (podemos até usar hidratopara o HTML gerado, para não recriar a árvore DOM do aplicativo).Vamos começar escrevendo uma função que retorna o HTML renderizado e o estado final.
Adicione novos argumentos e lógica ao nosso modelo, sobre o qual falamos acima:
Nosso servidor Express se torna um pouco mais complicado:
Mas o cliente é mais fácil:
Em seguida, você precisa limpar erros de plataforma cruzada como "o histórico não está definido". Para fazer isso, adicione uma função simples (até agora) em algum lugar utility.js
.
Depois, haverá um certo número de alterações de rotina que não trarei aqui (mas elas podem ser encontradas no commit correspondente ). Como resultado, nosso aplicativo React poderá renderizar no navegador e no servidor.Isso funciona!
Mas há, como se costuma dizer, uma ressalva ...
CARREGANDO? Tudo o que o Google vê no meu serviço de moda super bacana é CARREGANDO ?!Bem, parece que todo o nosso assincronismo jogou contra nós. Agora, precisamos de uma maneira de permitir que o servidor entenda que a resposta do back-end com o conteúdo do cartão precisa aguardar antes de renderizar o aplicativo React em uma string e enviá-lo ao cliente. E é desejável que esse método seja bastante geral.Pode haver muitas soluções. Uma abordagem é descrever em um arquivo separado para quais caminhos quais dados devem ser protegidos e fazer isso antes de renderizar o aplicativo ( artigo ). Esta solução tem muitas vantagens. É simples, é explícito e funciona.Como um experimento (o conteúdo original deve estar no artigo pelo menos em algum lugar!), Proponho outro esquema. Sempre que executamos algo assíncrono, que devemos esperar, adicione a promessa apropriada (por exemplo, a que retorna a busca) em algum lugar do nosso estado. Portanto, teremos um lugar onde você sempre pode verificar se tudo foi baixado.Adicione duas novas ações.
O primeiro será chamado quando a busca for iniciada, o segundo - no final dela .then()
.Agora adicione seu processamento ao redutor:
Agora vamos melhorar a ação fetchCard
:
Resta acrescentar initialState
promessas à matriz vazia e fazer o servidor esperar por todas! A função de renderização se torna assíncrona e assume o seguinte formato:
Devido à render
assincronia adquirida , o manipulador de solicitações também é um pouco mais complicado:
Et voilà!
Conclusão
Como você pode ver, a criação de um aplicativo de alta tecnologia não é tão simples. Mas não é tão difícil! O aplicativo final está no repositório do Github e, teoricamente, você só precisa do Docker para executá-lo.Se o artigo estiver com demanda, este repositório nem será abandonado! Poderemos analisar com algo de outro conhecimento necessário:- registro, monitoramento, teste de carga.
- teste, CI, CD.
- recursos mais legais, como autorização ou pesquisa de texto completo.
- configuração e desenvolvimento do ambiente de produção.
Obrigado pela atenção!