Skip to content

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):

assets/controllers/photo_preview_controller.js
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.

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version