Sprawić, by witryna działała jak SPA
Witryna jest szybka, ale każde kliknięcie wciąż przeładowuje całą stronę HTML. Tradycyjną odpowiedzią jest zbudowanie aplikacji jednostronicowej (ang. Single-Page Application, SPA): aplikacji JavaScript, napisanej w innym stosie, która komunikuje się z API. Oznacza to drugą aplikację do rozwijania, zabezpieczania, wdrażania i utrzymywania w synchronizacji z główną.
Symfony ma inną odpowiedź. Symfony UX to inicjatywa, która wnosi doświadczenie SPA do aplikacji renderowanych po stronie serwera: pisz dalej szablony Twig i kontrolery Symfony, a JavaScript dodawaj tylko tam, gdzie wnosi wartość.
Odkrywanie Symfony UX
Dobra wiadomość? Używamy Symfony UX już od pierwszego kroku tej książki. Szkielet webapp dostarcza dwa jego pakiety: symfony/stimulus-bundle i symfony/ux-turbo. Zajrzyj do importmap.php:
1 2 3 4 5 6
return [
'app' => ['path' => './assets/app.js', 'entrypoint' => true],
'@hotwired/stimulus' => ['version' => '3.2.2'],
'@symfony/stimulus-bundle' => ['path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js'],
'@hotwired/turbo' => ['version' => '8.0.23'],
];
Stimulus to mały framework JavaScript, który łączy kontrolery JavaScript z elementami HTML za pomocą atrybutów data-. Plik assets/stimulus_bootstrap.js uruchamia aplikację Stimulus i automatycznie rejestruje każdy kontroler umieszczony w katalogu assets/controllers/.
Turbo buduje na tej podstawie, aby nawigacja była natychmiastowa.
Natychmiastowa nawigacja z Turbo
Turbo Drive po cichu działało przez cały czas: przechwytuje każde kliknięcie linku i wysłanie formularza, pobiera stronę w tle i podmienia <body> bez pełnego przeładowania strony. Przeglądarka utrzymuje przy życiu ten sam JavaScript i CSS, więc nawigacja wydaje się natychmiastowa.
Otwórz narzędzia deweloperskie przeglądarki, przełącz się na zakładkę "Network" i poklikaj po witrynie: zmiany stron to żądania fetch, a nie pełne ładowania dokumentu.
Turbo prosi w zamian tylko o jedno: poprawną semantykę HTTP dla formularzy. Udane wysłanie musi przekierowywać (nasze robi to dzięki redirectToRoute()), a nieudane musi użyć kodu statusu 4xx. Symfony obsługuje to drugie automatycznie: gdy przesłany formularz jest nieprawidłowy, render() odpowiada kodem statusu 422 Unprocessable Entity.
Aktualizowanie części strony za pomocą Turbo Frames
Turbo Drive unika pełnych przeładowań strony. Turbo Frames idą o krok dalej: pozwalają fragmentowi strony nawigować niezależnie od reszty.
Lista komentarzy to doskonały kandydat. Podczas przeglądania stron komentarzy za pomocą linków "Poprzednia" i "Następna" powinna zmieniać się tylko lista; ponowne renderowanie nagłówka, formularza i stopki to zmarnowana praca. Opakuj komentarze w ramkę:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
--- i/templates/conference/show.html.twig
+++ w/templates/conference/show.html.twig
@@ -17,3 +17,4 @@
<div class="row">
<div class="col-12 col-lg-8">
+ <turbo-frame id="comments" data-turbo-action="advance">
{% if comments|length > 0 %}
@@ -56,5 +57,6 @@
No comments have been posted yet for this conference.
</div>
{% endif %}
+ </turbo-frame>
</div>
<div class="col-12 col-lg-4">
Linki wewnątrz elementu <turbo-frame> aktualizują tylko tę ramkę: po kliknięciu "Następna" Turbo pobiera stronę w tle i podmienia pasującą ramkę, pozostawiając resztę strony nietkniętą.
Atrybut data-turbo-action="advance" podnosi nawigację ramki do pełnej wizyty: adres URL w pasku adresu jest aktualizowany (wraz z parametrem zapytania offset), dzięki czemu paginowane komentarze pozostają możliwe do udostępnienia, a przycisk wstecz przeglądarki działa zgodnie z oczekiwaniami.
Podgląd zdjęcia komentarza za pomocą Stimulus
Czas napisać nasze pierwsze linie JavaScript w całej książce. Uczestnicy przesyłający zdjęcie nie widzą go przed opublikowaniem komentarza. Wyświetlmy podgląd, gdy wybiorą plik.
Utwórz kontroler Stimulus (nazwa pliku określa nazwę kontrolera, photo-preview):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
show() {
const [photo] = this.element.files;
if (!photo) {
this.preview?.remove();
this.preview = null;
return;
}
if (!this.preview) {
this.preview = document.createElement('img');
this.preview.className = 'img-thumbnail mt-2';
this.preview.style.maxHeight = '150px';
this.element.insertAdjacentElement('afterend', this.preview);
}
this.preview.src = URL.createObjectURL(photo);
}
}
To cały JavaScript, jakiego potrzebujemy. Stimulus odkrywa i rejestruje kontroler automatycznie. Połącz go z polem zdjęcia formularza komentarza za pomocą atrybutów data-:
1 2 3 4 5 6 7 8 9 10 11 12 13
--- i/src/Form/CommentType.php
+++ w/src/Form/CommentType.php
@@ -26,6 +26,10 @@ class CommentType extends AbstractType
->add('photo', FileType::class, [
'required' => false,
'mapped' => false,
+ 'attr' => [
+ 'data-controller' => 'photo-preview',
+ 'data-action' => 'change->photo-preview#show',
+ ],
'constraints' => [
new Image(maxSize: '1024k')
],
Atrybut data-controller wiąże kontroler z polem pliku, a data-action wywołuje jego metodę show() za każdym razem, gdy zmienia się wartość pola. Wybierz zdjęcie na stronie konferencji i ciesz się podglądem.
Kontrolery Stimulus współpracują ręka w rękę z Turbo: ponieważ Drive i Frames aktualizują stronę bez jej przeładowania, kontrolery są automatycznie podłączane i odłączane, gdy elementy pojawiają się i znikają z DOM.
Co z aplikacjami mobilnymi?
Uzyskaliśmy doświadczenie SPA bez budowania drugiej aplikacji: żadnego osobnego stosu JavaScript, żadnego drugiego serwera WWW, żadnej konfiguracji CORS, niczego nowego do wdrożenia.
A jeśli kiedykolwiek będziesz potrzebować natywnej aplikacji mobilnej, API utworzone w poprzednim kroku jest właściwym punktem wejścia: jest publiczne, udokumentowane i niezależne od frameworka. Witryna i aplikacja mobilna mogą rozwijać się niezależnie, współdzieląc to samo zaplecze.