Jadi, Anda memutuskan untuk membuat proyek baru. Dan proyek ini adalah aplikasi web. Berapa banyak waktu yang diperlukan untuk membuat prototipe dasar? Seberapa sulit? Apa yang harus dapat dilakukan oleh situs web modern sejak awal?
Pada artikel ini, kami akan mencoba menguraikan boilerplate dari aplikasi web sederhana dengan arsitektur berikut:
Apa yang akan kita bahas:
- mengatur lingkungan dev di docker-compose.
- pembuatan backend pada Flask.
- membuat tampilan depan pada Express.
- Bangun JS menggunakan Webpack.
- Bereaksi, Redux, dan rendering sisi server.
- tugas antrian dengan RQ.
Pendahuluan
Sebelum pengembangan, tentu saja, Anda harus terlebih dahulu memutuskan apa yang kami kembangkan! Sebagai aplikasi model untuk artikel ini, saya memutuskan untuk membuat mesin wiki primitif. Kami akan mengeluarkan kartu dalam penurunan harga; mereka dapat ditonton dan (suatu saat nanti) menawarkan suntingan. Semua ini akan kami atur sebagai aplikasi satu halaman dengan rendering sisi server (yang mutlak diperlukan untuk mengindeks terabyte konten kami di masa mendatang).
Mari kita lihat lebih detail komponen yang kita butuhkan untuk ini:
- Pelanggan Kami akan membuat aplikasi satu halaman (mis. Dengan transisi halaman menggunakan AJAX) pada bundel React + Redux , yang sangat umum di dunia front-end.
- Frontend . Mari kita membuat server Express sederhana yang akan membuat aplikasi Bereaksi kami (meminta semua data yang diperlukan di backend secara tidak sinkron) dan mengeluarkannya kepada pengguna.
- Backend . Master logika bisnis, backend kami akan menjadi aplikasi Flask kecil. Kami akan menyimpan data (kartu kami) di repositori dokumen MongoDB yang populer, dan untuk antrian tugas dan, mungkin, di masa mendatang, caching, kami akan menggunakan Redis .
- PEKERJA . Wadah terpisah untuk tugas berat akan diluncurkan oleh perpustakaan RQ .
Infrastruktur: git
Mungkin, kita tidak bisa membicarakan hal ini, tetapi, tentu saja, kita akan melakukan pengembangan di repositori git.
git init git remote add origin git@github.com:Saluev/habr-app-demo.git git commit --allow-empty -m "Initial commit" git push
(Di sini Anda harus segera mengisi
.gitignore
.)
Draf akhir dapat dilihat
di Github . Setiap bagian dari artikel sesuai dengan satu komit (saya banyak rebazed untuk mencapai ini!).
Infrastruktur: komposisi buruh pelabuhan
Mari kita mulai dengan mengatur lingkungan. Dengan banyaknya komponen yang kita miliki, solusi pengembangan yang sangat logis adalah menggunakan docker-compose.
Tambahkan file
docker-compose.yml
ke repositori
docker-compose.yml
konten berikut:
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
Mari kita lihat apa yang terjadi di sini.
- Wadah MongoDB dan wadah Redis dibuat.
- Sebuah wadah untuk backend kami dibuat (yang kami jelaskan di bawah). Variabel lingkungan APP_ENV = dev diteruskan ke sana (kami akan melihatnya untuk memahami pengaturan Flask apa yang akan dimuat), dan porta 40001 akan terbuka di luar (melalui itu klien browser kami akan pergi ke API).
- Wadah dari frontend kami dibuat. Berbagai variabel lingkungan juga dimasukkan ke dalamnya, yang akan berguna bagi kita nanti, dan port 40002 terbuka. Ini adalah port utama aplikasi web kita: di browser kita akan pergi ke http: // localhost: 40002 .
- Wadah pekerja kami dibuat. Dia tidak membutuhkan port eksternal, dan hanya akses yang diperlukan di MongoDB dan Redis.
Sekarang mari kita buat file docker. Saat ini,
serangkaian terjemahan artikel hebat tentang Docker akan hadir di HabrΓ© - Anda dapat dengan aman pergi ke sana untuk semua detailnya.
Mari kita mulai dengan 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
Dapat dipahami bahwa kita menjalankan aplikasi flask gunicorn, bersembunyi di bawah nama
app
di modul
backend.server
.
docker/backend/.dockerignore
tidak kalah pentingnya:
.git .idea .logs .pytest_cache frontend tests venv *.pyc *.pyo
Pekerja itu umumnya mirip dengan backend, hanya saja bukannya gunicorn kita memiliki peluncuran modul pit yang biasa:
# 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
Kami akan melakukan semua pekerjaan di
worker/__main__.py
.
Pekerja
.dockerignore
benar-benar mirip dengan backend
.dockerignore
.
Akhirnya, frontend. Ada
seluruh artikel terpisah tentang dia di HabrΓ©, tetapi menilai dengan
diskusi yang luas tentang StackOverflow dan komentar dalam semangat "Guys, apakah sudah 2018, apakah masih ada solusi normal?" semuanya tidak begitu sederhana di sana. Saya menyelesaikan versi file buruh pelabuhan ini.
# 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
Pro:
- semuanya di-cache seperti yang diharapkan (di lapisan bawah - dependensi, di atas - pembuatan aplikasi kita);
docker-compose exec frontend npm install --save newDependency
berfungsi sebagaimana mestinya dan memodifikasi package.json
dalam repositori kami (yang tidak akan menjadi masalah jika kami menggunakan COPY, seperti yang disarankan banyak orang). Akan tidak diinginkan untuk menjalankan npm install --save newDependency
luar wadah, karena beberapa dependensi dari paket baru mungkin sudah ada dan dibangun di bawah platform yang berbeda (di bawah yang di dalam buruh pelabuhan, dan tidak di bawah macbook kami yang bekerja, misalnya ), namun kami umumnya tidak ingin memerlukan kehadiran Node pada mesin pengembangan. Satu Docker untuk menguasai semuanya!
Baik dan tentu saja
docker/frontend/.dockerignore
:
.git .idea .logs .pytest_cache backend worker tools node_modules npm-debug tests venv
Jadi, kerangka kontainer kami sudah siap dan Anda dapat mengisinya dengan konten!
Backend: Kerangka kerja flask
Tambahkan
flask
,
flask-cors
gevent
,
gevent
dan
gunicorn
ke
requirements.txt
dan buat aplikasi Flask sederhana di
backend/server.py
.
Kami memberi tahu Flask untuk menarik pengaturan dari file
backend.{env}_settings
, yang berarti kami juga perlu membuat (
backend/dev_settings.py
) file
backend/dev_settings.py
agar semuanya lepas landas.
Sekarang kita dapat secara resmi NAIK backend kami!
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
Kami melanjutkan.
Frontend: Express framework
Mari kita mulai dengan membuat paket. Setelah membuat folder frontend dan menjalankan
npm init
di dalamnya, setelah beberapa pertanyaan tidak canggih, kami mendapatkan paket jadi.
{ "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" }
Di masa depan, kami sama sekali tidak membutuhkan Node.js di mesin pengembang (meskipun kami masih bisa mengelak dan memulai
npm init
melalui Docker, tapi oh well).
Di
Dockerfile
kami menyebutkan
npm run build
dan
npm run start
- Anda perlu menambahkan perintah yang sesuai ke
package.json
:
Perintah
build
belum melakukan apa-apa, tetapi masih akan bermanfaat bagi kita.
Tambahkan dependensi
Express dan buat aplikasi sederhana di
index.js
:
Sekarang
docker-compose up frontend
memunculkan frontend kami! Selain itu, di
http: // localhost: 40002 , klasik "Hello, world" seharusnya sudah pamer.
Frontend: build with webpack dan React application
Saatnya untuk menggambarkan sesuatu yang lebih dari sekadar teks dalam aplikasi kita. Di bagian ini, kita akan menambahkan komponen Bereaksi paling sederhana dari
App
dan mengkonfigurasi perakitan.
Ketika pemrograman dalam Bereaksi, sangat mudah untuk menggunakan
JSX , dialek JavaScript yang diperpanjang oleh konstruksi sintaksis formulir
render() { return <MyButton color="blue">{this.props.caption}</MyButton>; }
Namun, mesin JavaScript tidak memahaminya, jadi biasanya fase build ditambahkan ke frontend. Kompiler JavaScript khusus (yeah-yeah) mengubah gula sintaksis menjadi JavaScript klasik yang
jelek , menangani impor, mengecilkan, dan sebagainya.
Tahun 2014. java pencarian apt-cacheJadi, komponen Bereaksi paling sederhana terlihat sangat sederhana.
Dia hanya akan menampilkan salam kami dengan pin yang lebih meyakinkan.
Tambahkan
frontend/src/template.js
file
frontend/src/template.js
berisi kerangka kerja HTML minimum aplikasi masa depan kita:
Tambahkan titik masuk klien:
Untuk membangun semua keindahan ini, kita perlu:
webpack adalah
pembangun pemuda modis untuk JS (meskipun saya belum membaca artikel di frontend selama tiga jam, jadi saya tidak yakin tentang fashion);
babel adalah kompiler untuk semua jenis lotion seperti JSX, dan pada saat yang sama penyedia polyfill untuk semua case IE.
Jika iterasi frontend sebelumnya masih berjalan, yang harus Anda lakukan adalah
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
untuk menginstal dependensi baru. Sekarang konfigurasikan webpack:
Untuk membuat babel berfungsi, Anda perlu mengonfigurasi
frontend/.babelrc
:
{ "presets": ["@babel/env", "@babel/react"] }
Akhirnya, buat
npm run build
perintah kami bermakna:
// frontend/package.json ... "scripts": { "build": "webpack", "start": "node /app/server.js", "test": "echo \"Error: no test specified\" && exit 1" }, ...
Sekarang klien kami, bersama dengan bundel polyfill dan semua dependensinya, berjalan melalui babel, mengkompilasi dan melipat menjadi file minified monolitik
../dist/client.js
. Tambahkan kemampuan untuk mengunggahnya sebagai file statis ke aplikasi Express kami, dan dalam rute default kami akan mulai mengembalikan HTML kami:
Sukses! Sekarang, jika kita menjalankan
docker-compose up --build frontend
, kita akan melihat "Halo, dunia!" di bungkus baru, mengkilap, dan jika Anda memiliki ekstensi Alat Pengembang Bereaksi diinstal (
Chrome ,
Firefox ), maka ada juga komponen komponen Bereaksi dalam alat pengembang:

Backend: Data dalam MongoDB
Sebelum pindah dan menghidupkan kehidupan ke aplikasi kami, Anda harus terlebih dahulu menghirupnya ke backend. Tampaknya kami akan menyimpan kartu yang ditandai di Markdown - saatnya untuk melakukannya.
Meskipun
ada ORM untuk MongoDB dalam python , saya menganggap penggunaan ORM sebagai setan dan saya meninggalkan studi tentang solusi yang tepat untuk Anda. Sebagai gantinya, kami akan membuat kelas sederhana untuk kartu dan
DAO yang menyertainya:
(Jika Anda masih tidak menggunakan anotasi jenis dengan Python, pastikan untuk memeriksa
artikel ini !)
Sekarang mari kita membuat implementasi antarmuka
CardDAO
yang mengambil objek
Database
dari
pymongo
(ya, waktu untuk menambahkan
pymongo
ke
requirements.txt
):
Saatnya mendaftarkan konfigurasi Monga di pengaturan backend. Kami cukup memberi nama wadah kami dengan mongo
mongo
, jadi
MONGO_HOST = "mongo"
:
Sekarang kita perlu membuat
MongoCardDAO
dan memberikan akses aplikasi Flask padanya. Meskipun sekarang kita memiliki hierarki objek yang sangat sederhana (pengaturan β klien pymongo β database pymongo β
MongoCardDAO
), mari kita segera membuat komponen raja terpusat yang melakukan
injeksi ketergantungan (itu akan berguna lagi ketika kita melakukan pekerja dan peralatan).
Saatnya menambahkan rute baru ke aplikasi Flask dan nikmati pemandangannya!
Mulai ulang dengan
docker-compose up --build backend
:

Ups ... oh, tepatnya. Kami perlu menambahkan konten! Kami akan membuka folder alat dan menambahkan skrip ke dalamnya yang menambahkan satu kartu tes:
Perintah
docker-compose exec backend python -m tools.add_test_content
mengisi monga kami dengan konten dari dalam wadah
docker-compose exec backend python -m tools.add_test_content
.

Sukses! Sekarang adalah waktu untuk mendukung ini di ujung depan.
Frontend: Redux
Sekarang kita ingin membuat rute
/card/:id_or_slug
, dimana aplikasi Bereaksi kita akan terbuka, memuat data kartu dari API dan menunjukkannya kepada kita entah bagaimana. Dan di sini, mungkin, bagian yang paling sulit dimulai, karena kami ingin server segera memberi kami HTML dengan isi kartu, cocok untuk pengindeksan, tetapi pada saat yang sama, ketika aplikasi menavigasi antara kartu, ia menerima semua data dalam bentuk JSON dari API, dan halaman tidak kelebihan beban. Dan agar semua ini - tanpa copy-paste!
Mari kita mulai dengan menambahkan Redux. Redux adalah pustaka JavaScript untuk menyimpan keadaan. Idenya adalah bahwa alih-alih ribuan status tersirat bahwa komponen Anda berubah selama tindakan pengguna dan acara menarik lainnya, mereka memiliki satu status terpusat, dan melakukan perubahan melalui mekanisme tindakan terpusat. Jadi, jika sebelumnya untuk navigasi, pertama-tama kita menghidupkan loading GIF, maka kita membuat permintaan melalui AJAX dan, akhirnya, dalam keberhasilan panggilan balik, kita memperbarui bagian-bagian halaman yang diperlukan, maka dalam paradigma Redux kita diundang untuk mengirim tindakan "mengubah konten menjadi gif dengan animasi", yang akan mengubah status global sehingga salah satu komponen Anda akan membuang konten sebelumnya dan memasukkan animasinya, kemudian membuat permintaan, dan mengirim tindakan lain dalam panggilan balik keberhasilannya, "ubah konten menjadi dimuat". Secara umum, sekarang kita akan melihatnya sendiri.
Mari kita mulai dengan menginstal dependensi baru di wadah kami.
docker-compose exec frontend npm install --save \ redux \ react-redux \ redux-thunk \ redux-devtools-extension
Yang pertama adalah, pada kenyataannya, Redux, yang kedua adalah perpustakaan khusus untuk menyeberang React dan Redux (ditulis oleh para ahli kawin), yang ketiga adalah hal yang sangat diperlukan, kebutuhan yang dibuktikan dengan baik dalam
README-nya , dan, akhirnya, yang keempat adalah perpustakaan yang diperlukan agar
Redux DevTools dapat berfungsi.
Ekstensi .
Mari kita mulai dengan kode Redux boilerplate: membuat peredam yang tidak melakukan apa-apa, dan menginisialisasi keadaan.
Klien kami sedikit berubah, secara mental bersiap untuk bekerja dengan Redux:
Sekarang kita dapat menjalankan docker-compose up --build frontend untuk memastikan tidak ada yang rusak, dan status primitif kita muncul di Redux DevTools:

Frontend: Halaman Kartu
Sebelum Anda dapat membuat halaman dengan SSR, Anda perlu membuat halaman tanpa SSR! Akhirnya mari kita gunakan API kami yang cerdik untuk mengakses kartu dan membuat halaman kartu di ujung depan.
Saatnya mengambil keuntungan dari intelijen dan mendesain ulang struktur negara kita. Ada
banyak materi tentang hal ini, jadi saya sarankan untuk tidak menyalahgunakan kecerdasan dan akan fokus pada yang sederhana. Misalnya, seperti:
{ "page": { "type": "card", // // type=card: "cardSlug": "...", // "isFetching": false, // API "cardData": {...}, // ( ) // ... }, // ... }
Mari kita dapatkan komponen "kartu", yang mengambil konten cardData sebagai alat peraga (sebenarnya isi kartu kita di mongo):
Sekarang mari kita dapatkan komponen untuk seluruh halaman dengan kartu. Dia akan bertanggung jawab untuk mendapatkan data yang diperlukan dari API dan mentransfernya ke Kartu. Dan kami akan melakukan pengambilan data dengan cara React-Redux.
Pertama, buat file
frontend/src/redux/actions.js
dan buat tindakan yang mengekstraksi isi kartu dari API, jika belum:
export function fetchCardIfNeeded() { return (dispatch, getState) => { let state = getState().page; if (state.cardData === undefined || state.cardData.slug !== state.cardSlug) { return dispatch(fetchCard()); } }; }
Tindakan
fetchCard
, yang sebenarnya membuat pengambilan, sedikit lebih rumit:
function fetchCard() { return (dispatch, getState) => {
Oh, kami mendapat tindakan yang SESUATU! Ini harus didukung di peredam:
(Perhatikan sintaksis yang trendi untuk mengkloning objek dengan mengubah bidang individual.)Sekarang semua logika dilakukan dalam tindakan Redux, komponen itu sendiri CardPage
akan terlihat relatif sederhana:
Tambahkan pengolahan halaman.type sederhana ke komponen Aplikasi root kami:
Dan sekarang saat terakhir tetap - Anda perlu menginisialisasi page.type
dan page.cardSlug
tergantung pada URL halaman.Tetapi masih ada banyak bagian dalam artikel ini, tetapi kami tidak dapat membuat solusi berkualitas tinggi saat ini. Mari kita lakukan itu bodoh untuk saat ini. Itu benar-benar bodoh. Misalnya, biasa ketika menginisialisasi aplikasi!
Sekarang kita dapat membangun kembali frontend dengan bantuan docker-compose up --build frontend
untuk menikmati kartu kita helloworld
...
Jadi, tunggu sebentar ... dan di mana konten kita? Oh, kami lupa menguraikan Markdown!Pekerja: RQ
Parsing Markdown dan menghasilkan HTML untuk kartu dengan ukuran yang berpotensi tidak terbatas adalah tugas βberatβ yang khas, yang, alih-alih diselesaikan langsung di backend sambil menyimpan perubahan, biasanya diantrekan dan dieksekusi pada mesin kerja yang terpisah.Ada banyak implementasi open source dari antrian tugas; kami akan mengambil Redis dan perpustakaan sederhana RQ (Redis Queue), yang mentransmisikan parameter tugas dalam format acar dan mengatur proses pemijahan kami untuk diproses.Waktu untuk menambahkan lobak tergantung, pengaturan dan kabel!
Sedikit kode boilerplate untuk pekerja.
Untuk penguraian itu sendiri, hubungkan pustaka kabut dan tulis fungsi sederhana:
Logikanya: kita perlu CardDAO
mendapatkan kode sumber kartu dan menyimpan hasilnya. Tetapi objek yang berisi koneksi ke penyimpanan eksternal tidak dapat diserialkan melalui acar - yang berarti bahwa tugas ini tidak dapat segera diambil dan diantrekan untuk RQ. Dengan cara yang baik, kita perlu menciptakan Wiring
pekerja di samping dan melemparkannya dalam segala macam ... Ayo lakukan:
Kami menyatakan kelas pekerjaan kami, melemparkan kabel sebagai argumen kwarg tambahan dalam semua masalah. (Harap perhatikan bahwa ia membuat kabel BARU setiap saat, karena beberapa klien tidak dapat dibuat sebelum garpu yang terjadi di dalam RQ sebelum tugas diproses.) Sehingga semua tugas kami tidak bergantung pada kabel - yaitu, pada SEMUA objek kami - mari Mari kita membuat dekorator yang hanya akan mendapatkan yang dibutuhkan dari pemasangan kabel:
Tambahkan dekorator ke tugas kami dan nikmati hidup: 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)
Nikmati hidup? Ugh, saya ingin mengatakan, kami mulai pekerja: $ 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 ... dia tidak melakukan apa-apa! Tentu saja, karena kami tidak menetapkan satu tugas!Mari kita menulis ulang alat kita, yang membuat kartu uji, sehingga: a) tidak jatuh jika kartu sudah dibuat (seperti dalam kasus kami); b) menempatkan tugas pada penguraian marqdown.
Alat sekarang dapat dijalankan tidak hanya di backend, tetapi juga pada pekerja. Pada prinsipnya, sekarang kita tidak peduli. Kami meluncurkannya docker-compose exec worker python -m tools.add_test_content
dan di tab tetangga terminal kami melihat keajaiban - pekerja itu SESUATU! 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
Setelah membangun kembali wadah dengan backend, kita akhirnya dapat melihat isi kartu kita di browser:
Navigasi Frontend
Sebelum kita beralih ke SSR, kita perlu membuat semua keributan kita dengan Bereaksi setidaknya agak bermakna dan membuat aplikasi satu halaman kita benar-benar satu halaman. Mari kita perbarui alat kami untuk membuat dua (TIDAK SATU, DAN DUA! IBU, SAYA SEKARANG PEMBANGUN TANGGAL BESAR!) Kartu yang saling bertautan, dan kemudian kita akan berurusan dengan navigasi di antara mereka.Sekarang kita dapat mengikuti tautan dan merenungkan bagaimana setiap kali aplikasi indah kita restart. Hentikan itu!Pertama, letakkan handler Anda pada klik pada tautan. Karena HTML dengan tautan berasal dari backend, dan kami memiliki aplikasi pada Bereaksi, kami memerlukan sedikit fokus spesifik Bereaksi.
Karena semua logika dengan memuat kartu di komponen kami CardPage
, dalam aksi itu sendiri (luar biasa!), Tidak ada tindakan yang perlu diambil: export function navigate(link) { return { type: NAVIGATE, path: link.pathname } }
Tambahkan peredam konyol untuk kasus ini:
Karena sekarang keadaan aplikasi kita dapat berubah, CardPage
kita perlu menambahkan metode yang componentDidUpdate
identik dengan yang sudah kita tambahkan componentWillMount
. Sekarang, setelah memperbarui properti CardPage
(misalnya, properti cardSlug
selama navigasi), konten kartu dari backend juga akan diminta (hanya componentWillMount
melakukan ini ketika komponen diinisialisasi).Baiklah, docker-compose up --build frontend
dan kami memiliki navigasi yang berfungsi!
Pembaca yang penuh perhatian akan mencatat bahwa URL halaman tidak akan berubah ketika menavigasi antara kartu - bahkan dalam tangkapan layar kita melihat Halo, kartu dunia di alamat kartu demo. Dengan demikian, navigasi maju-mundur juga jatuh. Mari kita tambahkan beberapa ilmu hitam dengan sejarah segera untuk memperbaikinya!Hal paling sederhana yang dapat Anda lakukan adalah menambah aksi.navigate
sebuah tantangan history.pushState
. export function navigate(link) { history.pushState(null, "", link.href); return { type: NAVIGATE, path: link.pathname } }
Sekarang, ketika mengklik tautan, URL di bilah alamat browser akan benar-benar berubah. Namun, tombol kembali akan rusak !Untuk membuatnya berfungsi, kita perlu mendengarkan acara popstate
objek window
. Selain itu, jika dalam acara ini kita ingin melakukan navigasi mundur serta maju (yaitu, melalui dispatch(navigate(...))
), kita harus navigate
menambahkan bendera "jangan" khusus ke fungsi pushState
(jika tidak semuanya akan pecah bahkan lebih!). Selain itu, untuk membedakan antara status "kita", kita harus menggunakan kemampuan pushState
untuk menyimpan metadata. Ada banyak keajaiban dan debug, jadi mari kita langsung ke kode! Begini tampilan Aplikasi:
Dan inilah tindakan navigasi:
Sekarang ceritanya akan berhasil.Sentuhan terakhir: karena kita sekarang memiliki tindakan navigate
, mengapa kita tidak menyerahkan kode tambahan pada klien yang menghitung keadaan awal? Kami dapat memanggil navigasi ke lokasi saat ini:
Copy-paste hancur!Frontend: rendering sisi server
Saatnya chip utama kami (menurut saya) - SEO-friendly. Agar mesin telusur dapat mengindeks konten kami, yang sepenuhnya dibuat secara dinamis di komponen-React, kami harus dapat memberi mereka hasil rendering React, dan juga belajar bagaimana membuat hasil ini interaktif lagi.Skema umum sederhana. Pertama: kita perlu memasukkan HTML yang dihasilkan oleh komponen Bereaksi kita ke dalam template HTML kita App
. HTML ini akan dilihat oleh mesin pencari (dan browser dengan JS dimatikan, hehe). Kedua: tambahkan tag ke templat <script>
yang menyimpan suatu tempat (misalnya, suatu objek window
) suatu dump keadaan dari mana HTML ini dibuat. Kemudian kita dapat segera menginisialisasi aplikasi kita di sisi klien dengan keadaan ini dan menunjukkan apa yang diperlukan (kita bahkan dapat menggunakan hidratke HTML yang dihasilkan, agar tidak membuat ulang pohon DOM aplikasi).Mari kita mulai dengan menulis fungsi yang mengembalikan HTML yang dirender dan status akhir.
Tambahkan argumen dan logika baru ke template kita, yang kita bicarakan di atas:
Server Express kami menjadi sedikit lebih rumit:
Tetapi klien lebih mudah:
Selanjutnya, Anda perlu membersihkan kesalahan lintas-platform seperti "histori tidak ditentukan". Untuk melakukan ini, tambahkan fungsi sederhana (sejauh ini) di suatu tempat di utility.js
.
Kemudian akan ada sejumlah perubahan rutin yang saya tidak akan bawa ke sini (tetapi mereka dapat ditemukan di komit yang sesuai ). Akibatnya, aplikasi Bereaksi kami akan dapat merender di browser dan di server.Itu berhasil!
Tapi ada, seperti kata mereka, satu peringatan ...
PEMUATAN? Semua yang dilihat Google di layanan fesyen super keren saya adalah LOADING?!Yah, tampaknya semua asinkronisme kita telah melawan kita. Sekarang kita perlu cara untuk membuat server mengerti bahwa respons dari backend dengan konten kartu perlu menunggu sebelum merender aplikasi Bereaksi menjadi string dan mengirimkannya ke klien. Dan diharapkan bahwa metode ini cukup umum.Mungkin ada banyak solusi. Salah satu pendekatan adalah untuk menggambarkan dalam file terpisah yang jalur data apa yang harus diamankan, dan lakukan ini sebelum membuat aplikasi ( artikel ). Solusi ini memiliki banyak keunggulan. Sederhana, eksplisit, dan berfungsi.Sebagai percobaan (konten asli harus ada di artikel setidaknya di suatu tempat!) Saya mengusulkan skema lain. Mari kita setiap kali kita menjalankan sesuatu yang tidak sinkron, yang harus kita tunggu, tambahkan janji yang sesuai (misalnya, yang mengembalikan mengambil) di suatu tempat di negara kita. Jadi kami akan memiliki tempat di mana Anda selalu dapat memeriksa apakah semuanya telah diunduh.Tambahkan dua tindakan baru.
Yang pertama akan dipanggil saat pengambilan diluncurkan, yang kedua - di akhir itu .then()
.Sekarang tambahkan pemrosesan mereka ke peredam:
Sekarang kita akan meningkatkan tindakan fetchCard
:
Tetap menambahkan initialState
janji ke array kosong dan membuat server menunggu semuanya! Fungsi render menjadi asinkron dan mengambil bentuk berikut:
Karena render
asinkron yang diperoleh , penangan permintaan juga sedikit lebih rumit:
Dan lagi!
Kesimpulan
Seperti yang Anda lihat, membuat aplikasi berteknologi tinggi tidak sesederhana itu. Tetapi tidak begitu sulit! Aplikasi terakhir ada di repositori di Github dan, secara teoritis, Anda hanya perlu Docker untuk menjalankannya.Jika artikel ini diminati, repositori ini bahkan tidak akan ditinggalkan! Kami akan dapat melihatnya dengan sesuatu dari pengetahuan lain yang diperlukan:- logging, pemantauan, pengujian beban.
- pengujian, CI, CD.
- fitur keren seperti otorisasi atau pencarian teks lengkap.
- pengaturan dan pengembangan lingkungan produksi.
Terima kasih atas perhatian anda!