Entonces, decidiste hacer un nuevo proyecto. Y este proyecto es una aplicación web. ¿Cuánto tiempo llevará crear un prototipo básico? Que tan dificil es ¿Qué debe hacer un sitio web moderno desde el principio?
En este artículo, intentaremos esbozar la plantilla de una aplicación web simple con la siguiente arquitectura:
Lo que cubriremos:
- configurando el entorno de desarrollo en docker-compose.
- Creación de backend en Flask.
- creando una interfaz en Express.
- Construye JS usando Webpack.
- Reacción, Redux y representación del lado del servidor.
- colas de tareas con RQ.
Introduccion
Antes del desarrollo, por supuesto, ¡primero debes decidir qué estamos desarrollando! Como una aplicación modelo para este artículo, decidí hacer un motor wiki primitivo. Tendremos tarjetas emitidas en Markdown; se pueden ver y (en algún momento en el futuro) ofrecer ediciones. Todo esto lo organizaremos como una aplicación de una página con representación del lado del servidor (que es absolutamente necesaria para indexar nuestros futuros terabytes de contenido).
Echemos un vistazo un poco más detallado a los componentes que necesitamos para esto:
- Cliente Creemos una aplicación de una página (es decir, con transiciones de página usando AJAX) en el paquete React + Redux , que es muy común en el mundo front-end.
- Frontend Creemos un servidor Express simple que muestre nuestra aplicación React (solicitando todos los datos necesarios en el backend de forma asincrónica) y se la emita al usuario.
- Backend Maestro de la lógica de negocios, nuestro backend será una pequeña aplicación de Flask. Almacenaremos datos (nuestras tarjetas) en el popular repositorio de documentos MongoDB , y para la cola de tareas y, posiblemente, en el futuro, el almacenamiento en caché, usaremos Redis .
- TRABAJADOR La biblioteca RQ lanzará un contenedor separado para tareas pesadas.
Infraestructura: git
Probablemente, no podríamos hablar de esto, pero, por supuesto, llevaremos a cabo el desarrollo en el repositorio de git.
git init git remote add origin git@github.com:Saluev/habr-app-demo.git git commit --allow-empty -m "Initial commit" git push
(Aquí debe completar de inmediato
.gitignore
).
El borrador final se puede ver
en Github . Cada sección del artículo corresponde a una confirmación (¡rebauticé mucho para lograr esto!).
Infraestructura: docker-compose
Comencemos por configurar el entorno. Con la abundancia de componentes que tenemos, una solución de desarrollo muy lógica sería usar docker-compose.
Agregue el archivo
docker-compose.yml
al repositorio
docker-compose.yml
siguiente contenido:
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
Echemos un vistazo rápido a lo que está sucediendo aquí.
- Se crean un contenedor MongoDB y un contenedor Redis.
- Se crea un contenedor para nuestro backend (que describimos a continuación). Se le pasa la variable de entorno APP_ENV = dev (la veremos para comprender qué configuraciones de Flask cargar), y su puerto 40001 se abrirá afuera (a través de él, nuestro cliente de navegador irá a la API).
- Se crea un contenedor de nuestra interfaz. También se incluyen una variedad de variables de entorno, que nos serán útiles más adelante, y se abre el puerto 40002. Este es el puerto principal de nuestra aplicación web: en el navegador iremos a http: // localhost: 40002 .
- Se crea el contenedor de nuestro trabajador. No necesita puertos externos, y solo se requiere acceso en MongoDB y Redis.
Ahora creemos dockerfiles. En este momento, una
serie de traducciones de excelentes artículos sobre Docker está llegando a Habré: puede ir allí con seguridad para todos los detalles.
Comencemos con el backend.
# 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
Se entiende que corremos a través de la aplicación Gunicorn Flask, escondiéndonos bajo la
app
nombre en el módulo
backend.server
.
No menos importante
docker/backend/.dockerignore
:
.git .idea .logs .pytest_cache frontend tests venv *.pyc *.pyo
El trabajador generalmente es similar al backend, solo que en lugar de gunicorn tenemos el lanzamiento habitual de un módulo de pozo:
# 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
Haremos todo el trabajo en
worker/__main__.py
.
.dockerignore
trabajador
.dockerignore
es completamente similar al backend
.dockerignore
.
Finalmente, la interfaz. Hay un
artículo completamente separado sobre él sobre Habré, pero a juzgar por la
extensa discusión sobre StackOverflow y los comentarios en el espíritu de "Chicos, ¿ya es 2018, todavía no hay una solución normal?" No todo es tan simple allí. Me decidí por esta versión del archivo acoplable.
# 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
Pros:
- todo se almacena en caché como se esperaba (en la capa inferior - dependencias, en la parte superior - la compilación de nuestra aplicación);
docker-compose exec frontend npm install --save newDependency
funciona como debería y modifica package.json
en nuestro repositorio (que no sería el caso si docker-compose exec frontend npm install --save newDependency
COPY, como muchas personas sugieren). No sería deseable ejecutar npm install --save newDependency
fuera del contenedor de todos modos, porque algunas dependencias del nuevo paquete ya pueden estar presentes y construirse bajo una plataforma diferente (debajo de la del acoplador, y no bajo nuestro macbook de trabajo, por ejemplo ) y, sin embargo, generalmente no queremos exigir la presencia de Node en la máquina de desarrollo. ¡Un Docker para gobernarlos a todos!
Bueno y por supuesto
docker/frontend/.dockerignore
:
.git .idea .logs .pytest_cache backend worker tools node_modules npm-debug tests venv
Por lo tanto, nuestro marco contenedor está listo y puede llenarlo con contenido.
Backend: marco de matraz
Agregue
flask
,
flask-cors
,
gevent
y
gunicorn
a
gunicorn
y cree una aplicación Flask simple en
backend/server.py
.
Le dijimos a Flask que extraiga la configuración del archivo de fondo
backend.{env}_settings
, lo que significa que también tendremos que crear un archivo (al menos vacío)
backend/dev_settings.py
para que todo despegue.
¡Ahora podemos LEVANTAR oficialmente nuestro backend!
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
Seguimos adelante.
Frontend: marco Express
Comencemos creando un paquete. Después de haber creado la carpeta frontend y ejecutar
npm init
en ella, después de algunas preguntas poco sofisticadas, obtenemos el paquete.json terminado en el espíritu
{ "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" }
En el futuro, no necesitamos Node.js en absoluto en la máquina del desarrollador (aunque todavía podríamos esquivar e iniciar
npm init
través de Docker, pero bueno).
En
Dockerfile
mencionamos
npm run build
y
npm run start
: debe agregar los comandos apropiados a
package.json
:
El comando de
build
todavía no hace nada, pero aún nos será útil.
Agregue dependencias
Express y cree una aplicación simple en
index.js
:
¡Ahora
docker-compose up frontend
eleva nuestro frontend! Además, en
http: // localhost: 40002 , el clásico "Hola, mundo" ya debería presumir.
Frontend: compilación con webpack y aplicación React
Es hora de representar algo más que texto sin formato en nuestra aplicación. En esta sección, agregaremos el componente React más simple de la
App
y configuraremos el ensamblaje.
Al programar en React, es muy conveniente usar
JSX , un dialecto de JavaScript extendido por construcciones sintácticas del formulario
render() { return <MyButton color="blue">{this.props.caption}</MyButton>; }
Sin embargo, los motores de JavaScript no lo entienden, por lo que generalmente la fase de compilación se agrega a la interfaz. Los compiladores especiales de JavaScript (sí, sí) convierten el azúcar sintáctico en JavaScript
feo clásico, manejan importaciones, minimizan, etc.
2014 año. apt-cache search javaEntonces, el componente React más simple parece muy simple.
Simplemente mostrará nuestro saludo con un alfiler más convincente.
Agregue el archivo
frontend/src/template.js
contiene el marco HTML mínimo de nuestra futura aplicación:
Agregue un punto de entrada de cliente:
Para construir toda esta belleza, necesitamos:
webpack es un
creador juvenil de moda para JS (aunque no he leído artículos en la interfaz durante tres horas, por lo que no estoy seguro de la moda);
babel es un compilador para todo tipo de lociones como JSX y, al mismo tiempo, un proveedor de polyfill para todos los casos de IE.
Si la iteración anterior de la interfaz aún se está ejecutando, todo lo que tiene que hacer es
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
instalar nuevas dependencias Ahora configure webpack:
Para que Babel funcione, debe configurar
frontend/.babelrc
:
{ "presets": ["@babel/env", "@babel/react"] }
Finalmente, haga que nuestro
npm run build
significativo:
// frontend/package.json ... "scripts": { "build": "webpack", "start": "node /app/server.js", "test": "echo \"Error: no test specified\" && exit 1" }, ...
Ahora nuestro cliente, junto con un paquete de polyfills y todas sus dependencias, se ejecuta a través de babel, compila y se pliega en un archivo
../dist/client.js
monolítico
../dist/client.js
. Agregue la capacidad de cargarlo como un archivo estático a nuestra aplicación Express, y en la ruta predeterminada comenzaremos a devolver nuestro HTML:
Éxito! Ahora, si ejecutamos
docker-compose up --build frontend
, veremos "¡Hola, mundo!" en un envoltorio nuevo y brillante, y si tiene instalada la extensión React Developer Tools (
Chrome ,
Firefox ), también hay un árbol de componentes React en las herramientas de desarrollador:

Backend: Datos en MongoDB
Antes de continuar y dar vida a nuestra aplicación, primero debe respirarla por el backend. Parece que íbamos a almacenar las tarjetas marcadas en Markdown, es hora de hacerlo.
Si bien
hay ORM para MongoDB en python , considero que el uso de ORM es despiadado y le dejo el estudio de las soluciones adecuadas. En cambio, haremos una clase simple para la tarjeta y el
DAO que la acompaña:
(Si aún no utiliza anotaciones de tipo en Python, ¡asegúrese de consultar
estos artículos !)
Ahora
CardDAO
una implementación de la interfaz
CardDAO
que toma un objeto de
Database
de
pymongo
(sí, es hora de agregar
pymongo
a
pymongo
):
Es hora de registrar la configuración de Monga en la configuración del backend. Simplemente
MONGO_HOST = "mongo"
nuestro contenedor con mongo
mongo
, entonces
MONGO_HOST = "mongo"
:
Ahora necesitamos crear
MongoCardDAO
y darle acceso a la aplicación Flask. Aunque ahora tenemos una jerarquía de objetos muy simple (configuración → cliente de pymongo → base de datos de pymongo →
MongoCardDAO
),
MongoCardDAO
de inmediato un componente centralizado que realice la
inyección de dependencia (será útil nuevamente cuando hagamos el trabajo y las herramientas).
¡Es hora de agregar una nueva ruta a la aplicación Flask y disfrutar de la vista!
Reinicie con el
docker-compose up --build backend
:

Vaya ... oh, exactamente. ¡Necesitamos agregar contenido! Abriremos la carpeta de herramientas y agregaremos un script que agrega una tarjeta de prueba:
El
docker-compose exec backend python -m tools.add_test_content
llenará nuestra monga con contenido desde el interior del contenedor de
docker-compose exec backend python -m tools.add_test_content
.

Éxito! Ahora es el momento de apoyar esto en la parte frontal.
Frontend: Redux
Ahora queremos hacer la ruta
/card/:id_or_slug
, mediante la cual se abrirá nuestra aplicación React, cargar los datos de la tarjeta desde la API y
/card/:id_or_slug
alguna manera. Y aquí, quizás, la parte más difícil comienza, porque queremos que el servidor nos proporcione inmediatamente HTML con el contenido de la tarjeta, adecuado para la indexación, pero al mismo tiempo, cuando la aplicación navega entre las tarjetas, recibe todos los datos en forma de JSON de la API, y la página no se sobrecarga. Y para que todo esto, ¡sin copiar y pegar!
Comencemos agregando Redux. Redux es una biblioteca de JavaScript para almacenar estados. La idea es que, en lugar de los miles de estados implícitos que sus componentes cambian durante las acciones del usuario y otros eventos interesantes, tienen un estado centralizado y realizan cualquier cambio a través de un mecanismo centralizado de acciones. Entonces, si antes para la navegación primero activamos el GIF de carga, luego hicimos una solicitud a través de AJAX y finalmente, en la devolución de llamada exitosa, actualizamos las partes necesarias de la página, luego en el paradigma de Redux estamos invitados a enviar la acción "cambiar el contenido a un gif con animación", que cambiará el estado global para que uno de sus componentes arroje el contenido anterior y coloque la animación, luego haga una solicitud y envíe otra acción en su devolución de llamada exitosa, "cambie el contenido a cargado". En general, ahora lo veremos nosotros mismos.
Comencemos instalando nuevas dependencias en nuestro contenedor.
docker-compose exec frontend npm install --save \ redux \ react-redux \ redux-thunk \ redux-devtools-extension
El primero es, de hecho, Redux, el segundo es una biblioteca especial para cruzar React y Redux (escrito por expertos en apareamiento), el tercero es algo muy necesario, cuya necesidad está bien fundamentada en
su README , y, finalmente, el cuarto es la biblioteca necesaria para que
Redux DevTools funcione.
Extensión .
Comencemos con el código repetitivo de Redux: creando un reductor que no hace nada e inicializando el estado.
Nuestro cliente cambia un poco, preparándose mentalmente para trabajar con Redux:
Ahora podemos ejecutar docker-compose up --build frontend para asegurarnos de que nada esté roto, y nuestro estado primitivo ha aparecido en Redux DevTools:

Frontend: Página de tarjeta
¡Antes de poder crear páginas con SSR, debe crear páginas sin SSR! Finalmente usemos nuestra ingeniosa API para acceder a las tarjetas y compongamos la página de la tarjeta en el front-end.
Es hora de aprovechar la inteligencia y rediseñar nuestra estructura estatal. Hay
muchos materiales sobre este tema, por lo que sugiero no abusar de la inteligencia y me enfocaré en lo simple. Por ejemplo, tales:
{ "page": { "type": "card", // // type=card: "cardSlug": "...", // "isFetching": false, // API "cardData": {...}, // ( ) // ... }, // ... }
Consigamos el componente "tarjeta", que toma el contenido de cardData como accesorios (en realidad es el contenido de nuestra tarjeta en mongo):
Ahora obtengamos un componente para toda la página con la tarjeta. Será responsable de obtener los datos necesarios de la API y transferirlos a la Tarjeta. Y haremos la recuperación de datos en la forma React-Redux.
Primero, cree el archivo
frontend/src/redux/actions.js
y cree una acción que extraiga el contenido de la tarjeta de la API, si no es así:
export function fetchCardIfNeeded() { return (dispatch, getState) => { let state = getState().page; if (state.cardData === undefined || state.cardData.slug !== state.cardSlug) { return dispatch(fetchCard()); } }; }
La acción
fetchCard
, que en realidad hace que la búsqueda, sea un poco más complicada:
function fetchCard() { return (dispatch, getState) => {
¡Oh, tenemos una acción que ALGO HACE! Esto debe ser compatible con el reductor:
(Preste atención a la sintaxis moderna para clonar un objeto con campos individuales cambiantes).Ahora que toda la lógica se lleva a cabo en las acciones de Redux, el componente en sí CardPage
se verá relativamente simple:
Agregue un simple procesamiento page.type a nuestro componente raíz de la aplicación:
Y ahora queda el último momento: necesita inicializar de alguna manera page.type
y page.cardSlug
dependiendo de la URL de la página.Pero todavía hay muchas secciones en este artículo, pero no podemos hacer una solución de alta calidad en este momento. Hagámoslo estúpido por ahora. Eso es completamente estúpido. Por ejemplo, un habitual al inicializar la aplicación!
docker-compose up --build frontend
,
helloworld
…

, … ? , Markdown!
: RQ
Markdown HTML — «» , , , — .
; Redis
RQ (Redis Queue),
pickle .
, !
Un poco de código repetitivo para el trabajador.
Para el análisis en sí, conecta la biblioteca de mistune y escribe una función simple:
Lógicamente: necesitamos CardDAO
obtener el código fuente de la tarjeta y guardar el resultado. Pero un objeto que contiene una conexión a un almacenamiento externo no se puede serializar a través de pickle, lo que significa que esta tarea no se puede tomar y poner en cola de inmediato para RQ. En el buen sentido, necesitamos crear Wiring
un trabajador a un lado y tirarlo de todo tipo ... Hagámoslo:
Declaramos nuestra clase de trabajos, lanzando el cableado como un argumento adicional de kwargs en todos los problemas. (Tenga en cuenta que crea un cableado NUEVO cada vez, porque algunos clientes no pueden crearse antes de la bifurcación que ocurre dentro de RQ antes de que la tarea comience a procesarse). De modo que todas nuestras tareas no dependen del cableado, es decir, de TODOS nuestros objetos, vamos a Hagamos un decorador que obtenga solo lo necesario del cableado:
Agregue un decorador a nuestra tarea y disfrute de la 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)
¿Disfrutas la vida? Ugh, quería decir, comenzamos el trabajador: $ 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 ... no hace nada! Por supuesto, ¡porque no establecimos una sola tarea!Reescribamos nuestra herramienta, que crea una tarjeta de prueba, de modo que: a) no se caiga si la tarjeta ya está creada (como en nuestro caso); b) poner tarea en el análisis de marqdown.
Las herramientas ahora se pueden ejecutar no solo en el backend, sino también en el trabajador. En principio, ahora no nos importa. Lo lanzamos docker-compose exec worker python -m tools.add_test_content
y en una pestaña vecina de la terminal vemos un milagro: ¡el trabajador hizo 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
Después de reconstruir el contenedor con el backend, finalmente podemos ver el contenido de nuestra tarjeta en el navegador:
Navegación Frontend
Antes de pasar a SSR, debemos hacer que todo nuestro alboroto React sea un poco significativo y hacer que nuestra aplicación de una sola página sea realmente una sola página. Actualicemos nuestra herramienta para crear dos (¡NO UNA, Y DOS! ¡MAMÁ, AHORA GRANDE DESARROLLADOR DE FECHA!) Tarjetas que se unen entre sí, y luego nos ocuparemos de la navegación entre ellas.Ahora podemos seguir los enlaces y contemplar cómo cada vez que se reinicia nuestra maravillosa aplicación. Basta!Primero, coloque su controlador en los clics en los enlaces. Dado que HTML con enlaces proviene del backend, y la aplicación es con React, necesitamos un pequeño enfoque específico de React.
Dado que toda la lógica con la carga de las tarjetas en nuestro componente CardPage
, en la acción en sí (¡increíble!), No es necesario tomar ninguna acción: export function navigate(link) { return { type: NAVIGATE, path: link.pathname } }
Agregue un reductor tonto para este caso:
Como ahora el estado de nuestra aplicación puede cambiar, CardPage
necesitamos agregar un método componentDidUpdate
idéntico al que ya agregamos componentWillMount
. Ahora, después de actualizar las propiedades CardPage
(por ejemplo, propiedades cardSlug
durante la navegación), también se solicitará el contenido de la tarjeta desde el backend (lo componentWillMount
hizo solo cuando se inicializó el componente).Muy bien, docker-compose up --build frontend
y tenemos una navegación de trabajo!
Un lector atento notará que la URL de la página no cambiará al navegar entre tarjetas, incluso en la captura de pantalla vemos Hello, la tarjeta mundial en la dirección de la tarjeta de demostración. En consecuencia, la navegación hacia adelante y hacia atrás también se cayó. ¡Agreguemos algo de magia negra con historia de inmediato para solucionarlo!Lo más simple que puede hacer es agregar a la acción.navigate
Un desafío history.pushState
. export function navigate(link) { history.pushState(null, "", link.href); return { type: NAVIGATE, path: link.pathname } }
Ahora, al hacer clic en los enlaces, la URL en la barra de direcciones del navegador realmente cambiará. ¡Sin embargo, el botón de retroceso se romperá !Para que funcione, necesitamos escuchar el evento del popstate
objeto window
. Además, si en este caso queremos hacer la navegación hacia atrás y hacia adelante (es decir, a través dispatch(navigate(...))
), tendremos que navigate
agregar un indicador especial de "no pushState
" a la función (de lo contrario, ¡todo se romperá aún más!). Además, para distinguir entre "nuestros" estados, debemos usar la capacidad pushState
de guardar metadatos. Hay mucha magia y depuración, ¡así que vamos directamente al código! Así se verá la aplicación:
Y aquí está la acción de navegación:
Ahora la historia funcionará.Bueno, el último toque: dado que ahora tenemos una acción navigate
, ¿por qué no renunciamos al código adicional en el cliente que calcula el estado inicial? Podemos llamar a navegar a la ubicación actual:
Copiar y pegar destruido!Frontend: representación del lado del servidor
( ) — SEO-. , React-, React, .
. : HTML- HTML, React-
App
. HTML ( JS, -). :
<script>
, - (,
window
) , HTML. (
hydrateal HTML generado, para no volver a crear el árbol DOM de la aplicación).Comencemos escribiendo una función que devuelva HTML renderizado y el estado final.
Agregue nuevos argumentos y lógica a nuestra plantilla, de la que hablamos anteriormente:
Nuestro servidor Express se vuelve un poco más complicado:
Pero el cliente es más fácil:
A continuación, debe limpiar los errores multiplataforma como "el historial no está definido". Para hacer esto, agregue una función simple (hasta ahora) en algún lugar utility.js
.
Lo siguiente será un cierto número de cambios de rutina que no traeré aquí (pero se pueden encontrar en la confirmación correspondiente ). Como resultado, nuestra aplicación React podrá renderizar tanto en el navegador como en el servidor.Funciona!
Pero hay, como dicen, una advertencia ...
¿CARGANDO? ¡Todo lo que Google ve en mi servicio de moda súper genial es CARGANDO?Bueno, parece que todo nuestro asincronismo ha jugado contra nosotros. Ahora necesitamos una manera de que el servidor comprenda que la respuesta del backend con el contenido de la tarjeta debe esperar antes de procesar la aplicación React en una cadena y enviarla al cliente. Y es deseable que este método sea bastante general.Puede haber muchas soluciones. Un enfoque es describir en un archivo separado para qué rutas se deben proteger los datos, y hacer esto antes de presentar la aplicación ( artículo ). Esta solución tiene muchas ventajas. Es simple, es explícito y funciona.Como experimento (¡el contenido original debe estar en el artículo al menos en algún lugar!) Propongo otro esquema. Cada vez que ejecutemos algo asíncrono, que debemos esperar, agreguemos la promesa adecuada (por ejemplo, la que devuelve la búsqueda) en algún lugar de nuestro estado. Entonces tendremos un lugar donde siempre puede verificar si todo se ha descargado.Agrega dos nuevas acciones.
El primero se llamará cuando se inicie la búsqueda, el segundo, al final .then()
.Ahora agregue su procesamiento al reductor:
Ahora mejoraremos la acción fetchCard
:
¡Queda por agregar initialState
promesas a la matriz vacía y hacer que el servidor las espere a todas! La función de procesamiento se vuelve asíncrona y toma la siguiente forma:
Debido a la render
asincronía adquirida , el controlador de solicitudes también es un poco más complicado:
Et voilà!
Conclusión
Como puede ver, crear una aplicación de alta tecnología no es tan simple. ¡Pero no es tan difícil! La aplicación final está en el repositorio en Github y, en teoría, solo necesita Docker para ejecutarla.Si el artículo tiene demanda, ¡este repositorio ni siquiera será abandonado! Podremos verlo con algo de otro conocimiento que sea necesario:- registro, monitoreo, prueba de carga.
- prueba, CI, CD.
- características más interesantes como la autorización o la búsqueda de texto completo.
- Configuración y desarrollo del entorno de producción.
Gracias por su atencion!