Vous avez donc décidé de faire un nouveau projet. Et ce projet est une application web. Combien de temps faut-il pour créer un prototype de base? Est-ce difficile? Que devrait faire un site Web moderne dÚs le départ?
Dans cet article, nous allons essayer de décrire le passe-partout d'une application web simple avec l'architecture suivante:
Ce que nous couvrirons:
- configuration de l'environnement de développement dans docker-compose.
- création de backend sur Flask.
- créer une interface sur Express.
- Construisez JS Ă l'aide de Webpack.
- React, Redux et rendu cÎté serveur.
- files d'attente de tĂąches avec RQ.
Présentation
Avant le dĂ©veloppement, bien sĂ»r, vous devez d'abord dĂ©cider de ce que nous dĂ©veloppons! En tant qu'application modĂšle pour cet article, j'ai dĂ©cidĂ© de crĂ©er un moteur wiki primitif. Nous aurons des cartes Ă©mises dans Markdown; ils peuvent ĂȘtre regardĂ©s et (dans le futur) proposer des modifications. Tout cela, nous organiserons une application d'une page avec un rendu cĂŽtĂ© serveur (ce qui est absolument nĂ©cessaire pour indexer nos futurs tĂ©raoctets de contenu).
Examinons un peu plus en détail les composants dont nous avons besoin pour cela:
- Client Créons une application d'une page (c'est-à -dire avec des transitions de page en utilisant AJAX) sur le bundle React + Redux , ce qui est trÚs courant dans le monde frontal.
- Frontend . Créons un simple serveur Express qui restituera notre application React (demandant toutes les données nécessaires dans le backend de maniÚre asynchrone) et les enverra à l'utilisateur.
- Backend . Maßtre de la logique métier, notre backend sera une petite application Flask. Nous allons stocker les données (nos cartes) dans le référentiel de documents populaire MongoDB , et pour la file d'attente des tùches et, éventuellement, à l'avenir, la mise en cache, nous utiliserons Redis .
- TRAVAILLEUR . Un conteneur séparé pour les tùches lourdes sera lancé par la bibliothÚque RQ .
Infrastructure: git
Nous ne pourrions probablement pas en parler, mais, bien sûr, nous effectuerons le développement dans le référentiel git.
git init git remote add origin git@github.com:Saluev/habr-app-demo.git git commit --allow-empty -m "Initial commit" git push
(Ici, vous devez immédiatement remplir
.gitignore
.)
Le projet final peut ĂȘtre consultĂ©
sur Github . Chaque section de l'article correspond à un commit (j'ai beaucoup rebazé pour y parvenir!).
Infrastructure: docker-compose
Commençons par configurer l'environnement. Avec l'abondance de composants que nous avons, une solution de développement trÚs logique serait d'utiliser docker-compose.
Ajoutez le fichier
docker-compose.yml
au référentiel
docker-compose.yml
contenu suivant:
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
Jetons un coup d'Ćil Ă ce qui se passe ici.
- Un conteneur MongoDB et un conteneur Redis sont créés.
- Un conteneur pour notre backend est créé (que nous décrivons ci-dessous). La variable d'environnement APP_ENV = dev lui est transmise (nous allons l'examiner pour comprendre quels paramÚtres Flask charger), et son port 40001 s'ouvrira à l'extérieur (à travers lui notre client de navigateur ira à l'API).
- Un conteneur de notre frontend est créé. Une variété de variables d'environnement y sont également ajoutées, qui nous seront utiles plus tard, et le port 40002 s'ouvre.C'est le port principal de notre application Web: dans le navigateur, nous irons à http: // localhost: 40002 .
- Le conteneur de notre travailleur est créé. Il n'a pas besoin de ports externes, et seul l'accÚs est requis dans MongoDB et Redis.
Créons maintenant des fichiers docker. à l'heure actuelle, une
série de traductions d' excellents articles sur Docker arrive à Habré - vous pouvez y aller en toute sécurité pour tous les détails.
Commençons par le 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
Il est entendu que nous parcourons l'application gunicorn Flask, en nous cachant sous le nom
app
dans le module
backend.server
.
docker/backend/.dockerignore
non moins important:
.git .idea .logs .pytest_cache frontend tests venv *.pyc *.pyo
Le travailleur est généralement similaire au backend, mais au lieu de gunicorn, nous avons le lancement habituel d'un module de fosse:
# 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
Nous ferons tout le travail dans
worker/__main__.py
.
.dockerignore
ouvrier
.dockerignore
est complĂštement similaire au backend
.dockerignore
.
Enfin, le frontend. Il y a
tout un article à son sujet sur Habré, mais à en juger par la
discussion approfondie sur StackOverflow et les commentaires dans l'esprit de "Les gars, est-ce déjà 2018, n'y a-t-il toujours pas de solution normale?" tout n'y est pas si simple. Je me suis installé sur cette version du fichier 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
Avantages:
- tout est mis en cache comme prévu (sur la couche inférieure - dépendances, sur le dessus - la construction de notre application);
docker-compose exec frontend npm install --save newDependency
fonctionne comme il se doit et modifie package.json
dans notre référentiel (ce qui ne serait pas le cas si nous utilisions COPY, comme beaucoup de gens le suggÚrent). Il ne serait pas souhaitable d'exécuter npm install --save newDependency
dehors du conteneur de toute façon, car certaines dĂ©pendances du nouveau package peuvent dĂ©jĂ ĂȘtre prĂ©sentes et ĂȘtre construites sous une plate-forme diffĂ©rente (sous celle Ă l'intĂ©rieur du docker, et non sous notre macbook de travail, par exemple ), et pourtant nous ne voulons gĂ©nĂ©ralement pas exiger la prĂ©sence de Node sur la machine de dĂ©veloppement. Un Docker pour les gouverner tous!
Eh bien et bien sûr
docker/frontend/.dockerignore
:
.git .idea .logs .pytest_cache backend worker tools node_modules npm-debug tests venv
Ainsi, notre cadre de conteneur est prĂȘt et vous pouvez le remplir de contenu!
Backend: framework Flask
Ajoutez
flask
,
flask-cors
,
gevent
et
gunicorn
Ă
requirements.txt
et créez une application Flask simple dans
backend/server.py
.
Nous avons demandé à Flask de récupérer les paramÚtres du fichier
backend.{env}_settings
, ce qui signifie que nous devrons également créer un fichier
backend/dev_settings.py
(au moins vide) pour que tout décolle.
Maintenant, nous pouvons officiellement augmenter notre 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
Nous continuons.
Frontend: framework Express
Commençons par créer un package. AprÚs avoir créé le dossier frontal et exécuté
npm init
dedans, aprĂšs quelques questions simples, nous obtenons le package.json fini dans l'esprit
{ "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" }
à l'avenir, nous n'aurons plus besoin de Node.js sur la machine du développeur (bien que nous puissions encore esquiver et démarrer
npm init
via Docker, mais bon).
Dans
Dockerfile
nous avons mentionné
npm run build
et
npm run start
- vous devez ajouter les commandes appropriĂ©es Ă
package.json
:
La commande
build
ne fait rien encore, mais elle nous sera toujours utile.
Ajoutez des dépendances
Express et créez une application simple dans
index.js
:
Maintenant, le
docker-compose up frontend
augmente notre frontend! De plus, sur
http: // localhost: 40002 , le classique "Hello, world" devrait déjà se montrer.
Frontend: build avec webpack et application React
Il est temps de représenter quelque chose de plus que du texte brut dans notre application. Dans cette section, nous allons ajouter le composant React le plus simple de l'
App
et configurer l'assemblage.
Lors de la programmation dans React, il est trĂšs pratique d'utiliser
JSX , un dialecte de JavaScript étendu par des constructions syntaxiques du formulaire
render() { return <MyButton color="blue">{this.props.caption}</MyButton>; }
Cependant, les moteurs JavaScript ne le comprennent pas, donc généralement la phase de construction est ajoutée au frontend. Des compilateurs JavaScript spéciaux (ouais-ouais) transforment le sucre syntaxique en
laid laid classique, gÚrent les importations, réduisent, etc.
2014 année. apt-cache java de rechercheAinsi, le composant React le plus simple semble trÚs simple.
Il affichera simplement notre message d'accueil avec une épingle plus convaincante.
Ajoutez le fichier
frontend/src/template.js
contenant le framework HTML minimum de notre future application:
Ajoutez un point d'entrée client:
Pour construire toute cette beauté, nous avons besoin de:
webpack est un
constructeur de jeunesse Ă la mode pour JS (bien que je
n'aie pas lu d'articles sur le frontend pendant trois heures, donc je ne suis pas sûr de la mode);
babel est un compilateur pour toutes sortes de lotions comme JSX, et en mĂȘme temps un fournisseur de polyfill pour tous les cas IE.
Si l'itération précédente du frontend est toujours en cours d'exécution, tout ce que vous avez à faire est de
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
pour installer de nouvelles dépendances. Maintenant, configurez webpack:
Pour faire fonctionner babel, vous devez configurer
frontend/.babelrc
:
{ "presets": ["@babel/env", "@babel/react"] }
Enfin,
npm run build
sens Ă notre commande
npm run build
:
// frontend/package.json ... "scripts": { "build": "webpack", "start": "node /app/server.js", "test": "echo \"Error: no test specified\" && exit 1" }, ...
Maintenant, notre client, avec un ensemble de polyfills et toutes ses dépendances, parcourt babel, compile et se plie en un fichier
../dist/client.js
monolithique
../dist/client.js
. Ajoutez la possibilité de le télécharger en tant que fichier statique dans notre application Express, et dans l'itinéraire par défaut, nous commencerons à renvoyer notre code HTML:
SuccĂšs! Maintenant, si nous
docker-compose up --build frontend
, nous verrons "Bonjour, monde!" dans un nouvel emballage brillant, et si l'extension React Developer Tools est installée (
Chrome ,
Firefox ), il y a aussi une arborescence de composants React dans les outils de développement:

Backend: données dans MongoDB
Avant de continuer et de donner vie à notre application, vous devez d'abord l'insuffler dans le backend. Il semble que nous allions stocker les cartes marquées dans Markdown - il est temps de le faire.
Bien
qu'il existe des ORM pour MongoDB en python , je considÚre que l'utilisation des ORM est vicieuse et je vous laisse étudier les solutions appropriées. Au lieu de cela, nous allons créer un cours simple pour la carte et le
DAO qui l'accompagne:
(Si vous n'utilisez toujours pas d'annotations de type en Python, assurez-vous de consulter
ces articles !)
CardDAO
maintenant une implémentation de l'interface
CardDAO
qui prend en entrée un objet
Database
de
pymongo
(oui, il est temps d'ajouter
pymongo
Ă
requirements.txt
):
Il est temps d'enregistrer la configuration Monga dans les paramÚtres du backend. Nous avons simplement nommé notre conteneur avec mongo
mongo
, donc
MONGO_HOST = "mongo"
:
Nous devons maintenant créer
MongoCardDAO
et lui donner accĂšs Ă l'application Flask. Bien que nous ayons maintenant une hiĂ©rarchie d'objets trĂšs simple (paramĂštres â client pymongo â base de donnĂ©es pymongo â
MongoCardDAO
), créons immédiatement un composant king centralisé qui effectue l'
injection de dépendances (il sera à nouveau utile lorsque nous ferons le travailleur et les outils).
Il est temps d'ajouter un nouvel itinéraire à l'application Flask et de profiter de la vue!
Redémarrez avec la commande
docker-compose up --build backend
:

Oups ... oh, exactement. Nous devons ajouter du contenu! Nous allons ouvrir le dossier des outils et y ajouter un script qui ajoute une carte de test:
La commande
docker-compose exec backend python -m tools.add_test_content
remplira notre monga de contenu à l'intérieur du conteneur
docker-compose exec backend python -m tools.add_test_content
.

SuccĂšs! Il est maintenant temps de soutenir cela sur le front-end.
Frontend: Redux
Maintenant, nous voulons créer la route
/card/:id_or_slug
, par laquelle notre application React va s'ouvrir, charger les donnĂ©es de la carte Ă partir de l'API et nous les montrer en quelque sorte. Et ici, peut-ĂȘtre, la partie la plus difficile commence, car nous voulons que le serveur nous donne immĂ©diatement du HTML avec le contenu de la carte, adaptĂ© Ă l'indexation, mais en mĂȘme temps, lorsque l'application navigue entre les cartes, elle reçoit toutes les donnĂ©es sous forme de JSON de l'API, et la page ne surcharge pas. Et pour que tout cela - sans copier-coller!
Commençons par ajouter Redux. Redux est une bibliothĂšque JavaScript pour stocker l'Ă©tat. L'idĂ©e est qu'au lieu des mille Ă©tats implicites que vos composants changent au cours des actions de l'utilisateur et d'autres Ă©vĂ©nements intĂ©ressants, ils ont un Ă©tat centralisĂ© et apportent toute modification via un mĂ©canisme centralisĂ© d'actions. Donc, si plus tĂŽt pour la navigation, nous avons d'abord activĂ© le GIF de chargement, nous avons ensuite fait une demande via AJAX et, enfin, dans le rappel de succĂšs, nous avons mis Ă jour les parties nĂ©cessaires de la page, puis dans le paradigme Redux, nous sommes invitĂ©s Ă envoyer l'action "changer le contenu en gif avec animation", qui changera l'Ă©tat global de sorte qu'un de vos composants jette le contenu prĂ©cĂ©dent et place l'animation, puis fait une demande et envoie une autre action dans son rappel de succĂšs, «changez le contenu en chargé». En gĂ©nĂ©ral, nous allons maintenant le voir nous-mĂȘmes.
Commençons par installer de nouvelles dépendances dans notre conteneur.
docker-compose exec frontend npm install --save \ redux \ react-redux \ redux-thunk \ redux-devtools-extension
Le premier est, en fait, Redux, le second est une bibliothÚque spéciale pour croiser React et Redux (écrit par des experts en accouplement), le troisiÚme est une chose trÚs nécessaire, dont la nécessité est bien justifiée dans
son README , et, enfin, le quatriÚme est la bibliothÚque nécessaire au fonctionnement de
Redux DevTools Extension .
Commençons par le code passe-partout Redux: créer un réducteur qui ne fait rien et initialiser l'état.
Notre client change un peu, se préparant mentalement à travailler avec Redux:
Nous pouvons maintenant exécuter docker-compose up --build frontend pour nous assurer que rien n'est cassé, et notre état primitif est apparu dans Redux DevTools:

Frontend: Page de carte
Avant de pouvoir créer des pages avec des SSR, vous devez créer des pages sans SSR! Utilisons enfin notre ingénieuse API pour accéder aux cartes et composons la page de la carte sur le front end.
Il est temps de profiter de l'intelligence et de repenser notre structure étatique. Il
y a beaucoup de documents sur ce sujet, donc je suggĂšre de ne pas abuser de l'intelligence et de me concentrer sur le simple. Par exemple, tels que:
{ "page": { "type": "card", // // type=card: "cardSlug": "...", // "isFetching": false, // API "cardData": {...}, // ( ) // ... }, // ... }
Prenons le composant «card», qui prend le contenu de cardData comme accessoire (c'est en fait le contenu de notre carte en mongo):
Maintenant, obtenons un composant pour toute la page avec la carte. Il sera responsable d'obtenir les données nécessaires de l'API et de les transférer sur Card. Et nous allons récupérer les données de la maniÚre React-Redux.
Tout d'abord, créez le fichier
frontend/src/redux/actions.js
et créez une action qui extrait le contenu de la carte de l'API, si ce n'est déjà fait:
export function fetchCardIfNeeded() { return (dispatch, getState) => { let state = getState().page; if (state.cardData === undefined || state.cardData.slug !== state.cardSlug) { return dispatch(fetchCard()); } }; }
L'action
fetchCard
, qui rend en fait la récupération, un peu plus compliquée:
function fetchCard() { return (dispatch, getState) => {
Oh, nous avons une action QUELQUE CHOSE FAIT! Cela doit ĂȘtre pris en charge dans le rĂ©ducteur:
(Faites attention à la syntaxe tendance pour cloner un objet avec des champs individuels changeants.)Maintenant que toute la logique est exécutée dans les actions Redux, le composant lui CardPage
- mĂȘme aura l'air relativement simple:
Ajoutez un simple traitement page.type Ă notre composant d'application racine:
Et maintenant, le dernier moment reste - vous devez en quelque sorte initialiser page.type
et en page.cardSlug
fonction de l'URL de la page.Mais il y a encore de nombreuses sections dans cet article, mais nous ne pouvons pas faire une solution de haute qualité pour le moment. Faisons-le stupide pour l'instant. C'est complÚtement stupide. Par exemple, un habitué lors de l'initialisation de l'application!
Maintenant, nous pouvons reconstruire l'interface avec l'aide docker-compose up --build frontend
pour profiter de notre carte helloworld
...
Alors, attendez une seconde ... et oĂč est notre contenu? Oh, nous avons oubliĂ© d'analyser Markdown!Travailleur: RQ
Analyser Markdown et gĂ©nĂ©rer du HTML pour une carte de taille potentiellement illimitĂ©e est une tĂąche «lourde» typique qui, au lieu d'ĂȘtre rĂ©solue directement sur le backend lors de l'enregistrement des modifications, est gĂ©nĂ©ralement mise en file d'attente et exĂ©cutĂ©e sur des machines de travail distinctes.Il existe de nombreuses implĂ©mentations open source de files d'attente de tĂąches; nous prendrons Redis et une simple bibliothĂšque RQ (Redis Queue), qui transmet les paramĂštres des tĂąches au format pickle et nous organise des processus de gĂ©nĂ©ration pour leur traitement.Il est temps d'ajouter des radis en fonction des rĂ©glages et du cĂąblage!
Un peu de code passe-partout pour le travailleur.
Pour l'analyse elle-mĂȘme, connectez la bibliothĂšque mistune et Ă©crivez une fonction simple:
Logiquement: il faut CardDAO
rĂ©cupĂ©rer le code source de la carte et sauvegarder le rĂ©sultat. Mais l'objet contenant la connexion au stockage externe ne peut pas ĂȘtre sĂ©rialisĂ© via pickle - ce qui signifie que cette tĂąche ne peut pas ĂȘtre immĂ©diatement prise et mise en file d'attente pour RQ. Dans le bon sens, nous devons crĂ©er Wiring
un travailleur sur le cÎté et le jeter de toutes sortes ... Faisons-le:
Nous avons dĂ©clarĂ© notre classe d'emplois, en jetant le cĂąblage comme un argument kwargs supplĂ©mentaire dans toutes les tĂąches. (Notez qu'il crĂ©e un NOUVEAU cĂąblage Ă chaque fois, car certains clients ne peuvent pas ĂȘtre créés avant le fork qui se produit dans RQ avant que la tĂąche ne commence le traitement.) Pour que toutes nos tĂąches ne dĂ©pendent pas du cĂąblage - c'est-Ă -dire de TOUS nos objets - disons Faisons un dĂ©corateur qui n'obtiendra que le nĂ©cessaire du cĂąblage:
Ajoutez un décorateur à notre tùche et profitez de la vie: 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)
Profitez de la vie? Ugh, je voulais dire, nous commençons le travailleur: $ 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 ... il ne fait rien! Bien sûr, car nous n'avons pas fixé une seule tùche!Réécrivons notre outil, qui crée une carte de test, pour qu'il: a) ne tombe pas si la carte est déjà créée (comme dans notre cas); b) mettre la tùche sur l'analyse de marqdown.
Les outils peuvent dĂ©sormais ĂȘtre exĂ©cutĂ©s non seulement sur le backend, mais Ă©galement sur le travailleur. En principe, maintenant on s'en fout. Nous le lançons docker-compose exec worker python -m tools.add_test_content
et dans un onglet voisin du terminal, nous voyons un miracle - le travailleur a fait QUELQUE CHOSE! 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
AprĂšs avoir reconstruit le conteneur avec le backend, nous pouvons enfin voir le contenu de notre carte dans le navigateur:
Navigation frontend
Avant de passer Ă la SSR, nous devons faire tout notre possible avec React au moins quelque chose de significatif et rendre notre application de page unique vraiment une seule page. Mettons Ă jour notre outil pour crĂ©er deux (PAS UN, DEUX! MOM, JE SUIS GRAND DĂVELOPPEUR!) Cartes qui se lient les unes aux autres, puis nous traiterons de la navigation entre elles.Maintenant, nous pouvons suivre les liens et voir comment chaque fois que notre merveilleuse application redĂ©marre. ArrĂȘte ça!Tout d'abord, mettez votre gestionnaire sur les clics sur les liens. Depuis HTML avec des liens vient du backend, et nous avons l'application sur React, nous avons besoin d'un peu de focus spĂ©cifique Ă React.
Ătant donnĂ© que toute la logique du chargement des cartes dans notre composant CardPage
, dans l'action elle-mĂȘme (incroyable!), Aucune action ne doit ĂȘtre prise: export function navigate(link) { return { type: NAVIGATE, path: link.pathname } }
Ajoutez un réducteur idiot pour ce cas:
Ătant donnĂ© que l'Ă©tat de notre application peut maintenant changer, CardPage
nous devons ajouter une méthode componentDidUpdate
identique à celle que nous avons déjà ajoutée componentWillMount
. Maintenant, aprÚs avoir mis à jour les propriétés CardPage
(par exemple, les propriétés cardSlug
pendant la navigation), le contenu de la carte du backend sera également demandé (il componentWillMount
ne l' a fait que lorsque le composant a été initialisé).D'accord, docker-compose up --build frontend
et nous avons une navigation qui fonctionne!
Un lecteur attentif notera que l'URL de la page ne changera pas lors de la navigation entre les cartes - mĂȘme dans la capture d'Ă©cran, nous voyons Bonjour, la carte du monde Ă l'adresse de la carte de dĂ©monstration. En consĂ©quence, la navigation avant-arriĂšre a Ă©galement chutĂ©. Ajoutons tout de suite de la magie noire avec l'histoire pour y remĂ©dier!La chose la plus simple que vous puissiez faire est d'ajouter Ă l'actionnavigate
un défi history.pushState
. export function navigate(link) { history.pushState(null, "", link.href); return { type: NAVIGATE, path: link.pathname } }
Maintenant, en cliquant sur les liens, l'URL dans la barre d'adresse du navigateur va vraiment changer. Cependant, le bouton de retour se cassera !Pour le faire fonctionner, nous devons écouter l'événement de l' popstate
objet window
. De plus, si dans cet événement nous voulons faire la navigation aussi bien en arriÚre qu'en avant (c'est-à -dire à travers dispatch(navigate(...))
), nous devrons navigate
ajouter un drapeau spécial «ne pas pushState
» à la fonction (sinon tout se cassera encore plus!). De plus, pour faire la distinction entre «nos» états, nous devons utiliser la possibilité pushState
de sauvegarder les métadonnées. Il y a beaucoup de magie et de débogage, alors passons au code! Voici à quoi ressemblera l'application:
Et voici l'action de navigation:
Maintenant, l'histoire fonctionnera.Eh bien, la derniĂšre touche: puisque nous avons maintenant une action navigate
, pourquoi ne pas abandonner le code supplémentaire dans le client qui calcule l'état initial? Nous pouvons simplement appeler naviguer vers l'emplacement actuel:
Copier-coller détruit!Frontend: rendu cÎté serveur
Il est temps pour nos puces principales (Ă mon avis) - SEO-friendly. Pour que les moteurs de recherche puissent indexer notre contenu, qui est complĂštement créé dynamiquement dans les composants React, nous devons ĂȘtre en mesure de leur donner le rĂ©sultat du rendu React, et aussi apprendre Ă rendre ce rĂ©sultat interactif Ă nouveau.Le schĂ©ma gĂ©nĂ©ral est simple. PremiĂšrement: nous devons insĂ©rer le code HTML gĂ©nĂ©rĂ© par notre composant React dans notre modĂšle HTML App
. Ce HTML sera vu par les moteurs de recherche (et les navigateurs avec JS désactivé, hehe). DeuxiÚmement: ajoutez une balise au modÚle <script>
qui enregistre quelque part (par exemple, un objet window
) un vidage d'Ă©tat Ă partir duquel ce HTML a Ă©tĂ© rendu. Ensuite, nous pouvons immĂ©diatement initialiser notre application cĂŽtĂ© client avec cet Ă©tat et montrer ce qui est nĂ©cessaire (nous pouvons mĂȘme utiliser l' hydrateau HTML gĂ©nĂ©rĂ©, afin de ne pas recrĂ©er l'arborescence DOM de l'application).Commençons par Ă©crire une fonction qui retourne le HTML rendu et l'Ă©tat final.
Ajoutez de nouveaux arguments et une nouvelle logique à notre modÚle, dont nous avons parlé ci-dessus:
Notre serveur Express devient un peu plus compliqué:
Mais le client est plus simple:
Ensuite, vous devez nettoyer les erreurs multiplates-formes telles que «l'historique n'est pas défini». Pour ce faire, ajoutez une fonction simple (jusqu'à présent) quelque part dans utility.js
.
Ensuite, il y aura un certain nombre de changements de routine que je n'apporterai pas ici (mais ils peuvent ĂȘtre trouvĂ©s dans le commit correspondant ). En consĂ©quence, notre application React sera en mesure d'afficher Ă la fois dans le navigateur et sur le serveur.Ăa marche!
Mais il y a, comme on dit, une mise en garde ...
CHARGEMENT? Tout ce que Google voit sur mon service de mode super cool, c'est LOADING?!Eh bien, il semble que tout notre asynchronisme a jouĂ© contre nous. Nous avons maintenant besoin d'un moyen pour que le serveur comprenne que la rĂ©ponse du backend avec le contenu de la carte doit attendre avant de rendre l'application React Ă une chaĂźne et de l'envoyer au client. Et il est souhaitable que cette mĂ©thode soit assez gĂ©nĂ©rale.Il peut y avoir de nombreuses solutions. Une approche consiste Ă dĂ©crire dans un fichier sĂ©parĂ© pour quels chemins quelles donnĂ©es doivent ĂȘtre sĂ©curisĂ©es, et cela avant de rendre l'application ( article ). Cette solution prĂ©sente de nombreux avantages. C'est simple, c'est explicite et ça marche.Ă titre expĂ©rimental (le contenu original devrait ĂȘtre dans l'article au moins quelque part!) Je propose un autre schĂ©ma. Ă chaque fois que nous exĂ©cutons quelque chose d'asynchrone, que nous devons attendre, ajoutons la promesse appropriĂ©e (par exemple, celle qui retourne chercher) quelque part dans notre Ă©tat. Nous aurons donc un endroit oĂč vous pourrez toujours vĂ©rifier si tout a Ă©tĂ© tĂ©lĂ©chargĂ©.Ajoutez deux nouvelles actions.
Le premier sera appelé lorsque le fetch est lancé, le second - à la fin de celui-ci .then()
.Ajoutez maintenant leur traitement au réducteur:
Maintenant, nous allons améliorer l'action fetchCard
:
Il reste Ă ajouter des initialState
promesses au tableau vide et Ă faire attendre le serveur! La fonction de rendu devient asynchrone et prend la forme suivante:
En raison de l' render
asynchronie acquise , le gestionnaire de requĂȘtes est Ă©galement lĂ©gĂšrement plus compliquĂ©:
Et voilĂ !
Conclusion
Comme vous pouvez le voir, crĂ©er une application de haute technologie n'est pas si simple. Mais pas si difficile! L'application finale se trouve dans le rĂ©fĂ©rentiel sur Github et, thĂ©oriquement, vous n'avez besoin que de Docker pour l'exĂ©cuter.Si l'article est en demande, ce rĂ©fĂ©rentiel ne sera mĂȘme pas abandonnĂ©! Nous pourrons l'examiner avec quelque chose d'autres connaissances nĂ©cessaires:- enregistrement, surveillance, test de charge.
- tests, CI, CD.
- des fonctionnalités plus intéressantes comme l'autorisation ou la recherche en texte intégral.
- configuration et développement de l'environnement de production.
Merci de votre attention!