W poprzedniej lekcji zbudowałeś swój pierwszy działający panel: kontenery
ref(), wąsy w szablonie, przycisk pod @click i klasę zależną od stanu misji. Panel ożył - ale ożył trochę magicznie. Wpisałeś kod, a obraz po prostu pojawił się na ekranie. Nie wiesz jeszcze, kto go tam wstawił ani w którym momencie.Na stacji badawczej takie "po prostu" nie przechodzi. Inżynier dyżurny musi wiedzieć, który przełącznik odpala system, w jakiej kolejności budzą się moduły i gdzie szukać usterki, gdy ekran panelu pozostaje czarny. Dziś, @name, rozbierzemy sekwencję zapłonu aplikacji Vue na pojedyncze kroki - i po tej lekcji żaden z nich nie będzie już magią.
Otwórz projekt, który wygenerował kreator z lekcji o Vite. Są w nim trzy pliki, których jeszcze ani razu nie tknąłeś:
index.html w katalogu głównym, src/main.js oraz src/App.vue. Żaden z nich sam z siebie nie wyświetla niczego. Dopiero we trójkę tworzą łańcuch, który zamienia Twój kod w obraz na monitorze centrum kontroli.Jedna uwaga porządkowa, zanim ruszymy. Jeśli w kreatorze zgodziłeś się na TypeScript, plik startowy nazywa się
main.ts, a bez TypeScriptu - main.js. To ten sam plik, ta sama rola i ta sama zawartość, różni się wyłącznie rozszerzenie. W tej lekcji piszę o main.js, bo tak nazywa się w ćwiczeniach, które dostaniesz zaraz po niej.Zacznijmy od początku łańcucha, czyli od tego, co jako pierwsze dostaje przeglądarka. Przeglądarka nie potrafi otworzyć pliku
.vue - to nie jest format, który zna. Umie jedno: wczytać stronę HTML. Dlatego każdy projekt Vue ma dokładnie jeden plik HTML i to on jest pierwszą rzeczą, jaka trafia na ekran.W środku warto zobaczyć cztery rzeczy. Znacznik
<meta charset="UTF-8"> ustawia kodowanie znaków, żeby polskie ogonki nie zamieniły się w krzaczki. Znacznik <meta name="viewport"> mówi urządzeniom mobilnym, żeby nie udawały szerokiego ekranu - bez tej linii panel na tablecie w module mieszkalnym wyświetli się jak pomniejszona wersja strony biurkowej. Znacznik <title> to napis na karcie przeglądarki. Najważniejsze są jednak dwie ostatnie linie: pusty <div> z atrybutem id o wartości app oraz znacznik <script> z atrybutem type="module".Ten pusty
<div> to platforma lądowania. Vue nie doklei interfejsu gdziekolwiek - potrzebuje jednego, wyraźnie wskazanego elementu, w którym ma prawo rządzić. Atrybut type="module" oznacza z kolei, że wskazany plik jest modułem ES, czyli że wolno w nim używać instrukcji import. Bez tego atrybutu przeglądarka zatrzymałaby się na pierwszej linii main.js i zgłosiła błąd składni.1<!DOCTYPE html>
2<html lang="pl">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>NOVA LAB - Mission Control</title>
7 </head>
8 <body>
9 <div id="app"></div>
10 <script type="module" src="/src/main.js"></script>
11 </body>
12</html>Przeczytaj ten plik jeszcze raz i zauważ, czego w nim nie ma. Nie ma ani jednego znacznika Vue, ani jednych wąsów, ani nazwy komponentu. To zwykły HTML, dokładnie taki, jaki pisałeś w pierwszym świecie - Vue niczego tu nie zmienia. Kluczowy
<div> jest pusty i pusty pozostanie na dysku; treść pojawi się w nim dopiero w przeglądarce, w chwili uruchomienia aplikacji.Wartość
app w atrybucie id nie jest magiczna. To zwykła nazwa, którą sam wybierasz, i mogłaby brzmieć control-room albo nova. Musi się natomiast zgadzać z tym, co za moment podasz w main.js - i to jedno z najczęstszych źródeł czarnego ekranu, do którego wrócimy pod koniec tej lekcji.Przeglądarka doszła do znacznika
<script> i poprosiła o plik /src/main.js. W trybie deweloperskim tę prośbę obsługuje Vite: znajduje plik, w razie potrzeby przepuszcza go przez kompilator i odsyła gotowy moduł. To pierwszy fragment Twojego własnego kodu, jaki w ogóle się wykona - wszystko, co widzisz później na ekranie, zaczyna się w tych kilku linijkach.Cały plik startowy to zwykle cztery, pięć linii. Prześledzimy je po kolei, bo każda robi dokładnie jedną rzecz.
Pierwsze dwie linie sprowadzają narzędzia. Instrukcja
import ma zawsze ten sam kształt i składa się z czterech części ustawionych w stałej kolejności: słowo import, lista nazw do sprowadzenia, słowo from, a na końcu źródło zapisane w cudzysłowie. Zapamiętaj tę czwórkę, bo napiszesz ją w każdym pliku Vue, jaki stworzysz.Różnica między dwiema liniami tkwi w klamrach. Funkcja
createApp jest jednym z wielu narzędzi udostępnianych przez pakiet vue, więc jej nazwę bierzemy w klamry - to tak zwany eksport nazwany, a klamry mówią "wybieram z tej paczki dokładnie to". Plik .vue jest inny: udostępnia na zewnątrz jedną jedyną rzecz, czyli swój komponent, i robi to jako eksport domyślny. Eksport domyślny sprowadzasz bez klamer, a nazwę nadajesz mu sam - App to tylko przyjęta konwencja, nic tej nazwy nie wymusza.Zwróć jeszcze uwagę na samo źródło. Zapis
'vue' bez ukośnika to nazwa pakietu, którego Vite poszuka w katalogu node_modules zainstalowanym przez npm install. Zapis './App.vue' zaczyna się od kropki z ukośnikiem, więc jest ścieżką względną: szukaj obok mnie, w tym samym katalogu. Rozszerzenie .vue piszemy tu jawnie, inaczej narzędzie nie zgadnie, o który plik chodzi.1import { createApp } from 'vue'
2import App from './App.vue'Po tych dwóch liniach na ekranie nie zmieniło się absolutnie nic i nie mogło się zmienić. Import to wyłącznie wniesienie narzędzi na pokład: masz teraz pod ręką funkcję
createApp oraz komponent App, ale żadnego z nich jeszcze nie użyłeś. Sam plik App.vue również pozostał nietknięty - nie zmieniasz go przez to, że go sprowadzasz.Mając narzędzia, możesz powołać do życia aplikację. Robi to funkcja
createApp, a jej jedynym zadaniem jest zwrócenie instancji aplikacji Vue - jednego obiektu, który jest centrum dowodzenia całego systemu. To on wie, jaki komponent stoi na szczycie, to on przyjmie dodatkowe moduły i to jego poprosisz o uruchomienie.W nawiasie podajesz komponent główny, czyli ten jeden, od którego zaczyna się cała hierarchia. U nas to
App. Wszystkie pozostałe komponenty stacji będą później wisiały pod nim, ale createApp interesuje wyłącznie ten pierwszy.Zanim zobaczysz kod, uporządkujmy, czego
createApp nie robi - bo ta funkcja bywa mylona z trzema zupełnie innymi. Nie tworzy bazy danych: wspólną pamięcią misji zajmuje się Pinia i jej własna funkcja createPinia, którą zobaczysz jeszcze w tej lekcji. Nie kompiluje CSS: style opisujesz w sekcji <style> komponentu, a na gotowy arkusz zamienia je Vite podczas budowania projektu. Nie tworzy też routingu: nawigacją między ekranami stacji zarządza Vue Router z funkcją createRouter, do której wrócimy w Centrum Nawigacji. Wszystkie trzy są osobnymi narzędziami i żadne z nich nie ukrywa się w createApp.1const app = createApp(App)Kształt tej linii jest identyczny jak przy
ref() z poprzedniej lekcji: słowo const, nazwa, znak równości, wywołanie funkcji z argumentem. Zmienna app trzyma teraz instancję aplikacji.I tu najważniejsza obserwacja całej lekcji: na ekranie nadal nie ma niczego. Instancja istnieje w pamięci przeglądarki, zna swój komponent główny, ale nie ma najmniejszego pojęcia, w którym miejscu strony ma się pokazać. Plik
index.html się nie zmienił, jego <div> jest wciąż pusty. Aplikacja jest zbudowana, lecz jeszcze nie wystartowała.Brakuje ostatniego ruchu: wskazania platformy. Robi to metoda
mount wywołana na instancji aplikacji. Podajesz jej selektor CSS - ten sam rodzaj zapisu, którego używałeś w arkuszach stylów. Znak # oznacza "element o tym identyfikatorze", więc '#app' czyta się jako "element o id równym app", czyli dokładnie ten pusty <div> z pliku HTML.Od tego wywołania Vue przejmuje wskazany element: wstawia do niego wynik szablonu komponentu głównego i od tej pory sam pilnuje jego zawartości. To jest moment startu systemu.
Zapamiętaj też, czego
mount nie robi, bo pomyłki idą tu zawsze w tę samą stronę. Nie tworzy żadnego pliku HTML - plik index.html musi istnieć wcześniej i to Ty go piszesz. Nie instaluje pakietów, bo od tego jest npm install z lekcji o Vite. Nie uruchamia też serwera deweloperskiego: serwer startuje poleceniem npm run dev i działa na długo przed tym, zanim Twój kod w ogóle się wykona.1import { createApp } from 'vue'
2import App from './App.vue'
3
4const app = createApp(App)
5
6app.mount('#app')To kompletny plik startowy najprostszej aplikacji Vue - cztery instrukcje i ani jednej więcej. Uruchom projekt, a w przeglądarce zobaczysz to, co masz w szablonie
App.vue. Zwróć uwagę, że nie zmieniło się nic poza tym jednym plikiem: index.html wygląda tak samo, App.vue tak samo, a mimo to system ruszył.Instancję można też zamontować bez zapisywania jej do zmiennej, łańcuchem
createApp(App).mount('#app'). Działa identycznie i w przykładach z dokumentacji zobaczysz taki zapis dość często, ale w NOVA LAB polecam Ci wersję dwuetapową ze zmienną app. Powód jest praktyczny: za chwilę będziesz do tej instancji dokładał moduły stacji, a do tego potrzebujesz uchwytu, który łańcuch po prostu wyrzuca.Skoro selektor jest zwykłym tekstem, to nikt nie broni Ci wpisać w nim czegokolwiek - i tu bierze się klasyczna awaria pierwszego dnia. Aplikacja się kompiluje, konsola nie krzyczy o składni, a strona jest pusta.
1// index.html contains <div id="app"></div>
2// but the selector below points to something else
3app.mount('#control-room')Vue szuka elementu
#control-room, nie znajduje go i nie ma gdzie postawić interfejsu, więc w konsoli przeglądarki pojawia się ostrzeżenie o braku elementu docelowego. Nie zmieniło się przy tym nic w Twoim komponencie - App.vue jest zupełnie poprawny, a mimo to nie zobaczysz z niego ani jednego znaku. Gdy więc ekran panelu pozostaje czarny, @name, zacznij od porównania dwóch miejsc: wartości id w index.html i tekstu w wywołaniu mount.Jeden panel to jeszcze nie stacja. Prędzej czy później system misji potrzebuje wspólnej pamięci, z której korzystają wszystkie moduły naraz, oraz nawigacji, która przełącza ekrany centrum kontroli. Takich rzeczy nie da się sensownie doczepić do jednego komponentu - one muszą obowiązywać w całej aplikacji.
Vue nazywa je pluginami, a w naszej terminologii są to moduły stacji dokładane do instancji przed startem. Podłącza się je metodą
use wywołaną na instancji aplikacji: piszesz nazwę zmiennej, kropkę, use, a w nawiasie podajesz gotowy moduł. Poznaj od razu dwa, które kreator z lekcji o Vite wpisał Ci do projektu.Pierwszym jest Pinia, czyli centralna baza danych misji. Funkcja
createPinia tworzy jej świeżą instancję - tak jak createApp tworzy instancję aplikacji - a wynik tego wywołania wędruje prosto do use. Drugim jest router, sprowadzany z katalogu ./router, który wygenerował kreator. To już gotowy obiekt, więc podajesz go bez żadnego wywołania.1import { createApp } from 'vue'
2import { createPinia } from 'pinia'
3import App from './App.vue'
4import router from './router'
5
6const app = createApp(App)
7
8app.use(createPinia())
9app.use(router)
10
11app.mount('#app')Popatrz, co się tu nie zmieniło: linia z
createApp i linia z mount są dokładnie takie same jak w wersji minimalnej, a App.vue nadal o niczym nie wie. Doszły wyłącznie dwie linie w środku - i to jest cała idea pluginów. Rozbudowujesz system, nie przebudowując go.Kolejność ma tu znaczenie i jest tylko jedna słuszna: wszystkie wywołania
use muszą stać przed mount. Montowanie to start, a modułów nie dokłada się do rakiety po odpaleniu silników. Gdybyś przestawił te linie, pierwsze renderowanie odbyłoby się bez pluginów - komponenty nie znalazłyby ani wspólnej pamięci, ani elementów nawigacji, a w konsoli wylądowałyby błędy o nierozpoznanych nazwach. Metoda use zwraca zresztą tę samą instancję, więc technicznie dałoby się łączyć wywołania w łańcuch, ale polecam pisać je jedno pod drugim: w sytuacji awaryjnej od razu widać listę podłączonych modułów.Jeśli w Twoim projekcie nie ma katalogu
router albo nie zainstalowałeś Pinii, po prostu usuń odpowiedni import razem z jego linią use - aplikacja zadziała bez nich, tak jak w wersji minimalnej. Obydwa moduły dostaną zresztą własne lokacje: Centrum Nawigacji zajmuje się routerem, a Baza Danych Misji - Pinią.Został ostatni z trzech plików.
App.vue to komponent główny, czyli jedyny, który montujesz bezpośrednio; wszystkie pozostałe będą później wisiały wewnątrz niego. Z tej roli wynika jego zawartość: trzymamy tu wyłącznie to, co jest wspólne dla całej stacji i nie znika przy przełączaniu ekranów. W praktyce jest to belka górna z nazwą i nawigacją, obszar główny na treść oraz stopka.Do zbudowania takiej ramy wystarczą znaczniki, które znasz z pierwszego świata:
<header>, <nav>, <main> i <footer>. Nowa jest tylko jedna rzecz - sposób podpięcia linków. W poprzedniej lekcji zapamiętałeś, że pod @click podaje się nazwę funkcji bez nawiasów. To prawda dopóty, dopóki funkcja nie potrzebuje argumentu. Gdy chcesz jej coś przekazać, piszesz normalne wywołanie z nawiasami i wartością w środku, na przykład @click="changeSection('Systems')". Vue potraktuje to jako wyrażenie i samo opakuje je w funkcję wywoływaną przy kliknięciu - dlatego nie uruchamia się ono od razu przy wyświetleniu strony.1<template>
2 <div class="nova-lab">
3 <header>
4 <h1>NOVA LAB</h1>
5 <nav>
6 <a href="#" @click="changeSection('Dashboard')">Dashboard</a>
7 <a href="#" @click="changeSection('Systems')">Systems</a>
8 <a href="#" @click="changeSection('Crew')">Crew</a>
9 </nav>
10 </header>
11
12 <main class="content">
13 <h2>{{ currentSection }}</h2>
14 <p>Section content will be loaded here.</p>
15 </main>
16
17 <footer>
18 <p>NOVA LAB Mission Control 2087</p>
19 </footer>
20 </div>
21</template>Cała rama to zwykły, semantyczny HTML - nie zmieniło się w nim nic poza trzema małpami i jedną parą wąsów. Nagłówek, nawigacja, obszar główny i stopka wyglądałyby identycznie na stronie bez żadnego frameworka. Vue dołożyło tu tylko dwie rzeczy: reakcję na kliknięcie oraz jedno miejsce, w którym podstawia bieżącą wartość.
Teraz druga sekcja pliku. Potrzebujemy jednego kontenera z nazwą aktualnej sekcji oraz funkcji, która tę nazwę podmienia. Funkcja przyjmuje argument - tę samą wartość, którą przekazałeś w szablonie - i zapisuje ją do kontenera przez
.value, zgodnie z regułą z poprzedniej lekcji: w skrypcie zawsze .value, w szablonie nigdy.1<script setup>
2import { ref } from 'vue'
3
4const currentSection = ref('Dashboard')
5
6function changeSection(name) {
7 currentSection.value = name
8}
9</script>Kliknij dowolny link, a nagłówek w obszarze głównym zmieni się na nazwę wybranej sekcji. Zauważ, co przy tym zostaje na miejscu: belka górna, nawigacja i stopka nie drgną nawet na moment, bo Vue odświeża wyłącznie ten fragment, który korzysta ze zmienionego kontenera. Dokładnie po to komponent główny jest ramą - rzeczy wspólne mają się nie przeładowywać.
Sekcję
<style scoped> pominąłem tu celowo, bo o stylach mówiliśmy w lekcji o modułach .vue i nic nowego by nie wniosła. W ćwiczeniu, które zaraz dostaniesz, style są już przygotowane.Na razie przełączamy tylko napis, bo to wszystko, czego potrzeba, żeby zrozumieć rolę komponentu głównego. W Centrum Nawigacji ta sama rama zmieni się w prawdziwą nawigację: znaczniki
<a> zastąpi komponent RouterLink, a w miejsce nagłówka w <main> wskoczy RouterView, który podmienia całe ekrany stacji zamiast jednej linijki tekstu. Kontener currentSection przestanie być wtedy potrzebny.Masz już wszystkie elementy, więc złóżmy je w jedną listę kontrolną. Tak wygląda start systemu NOVA LAB, krok po kroku, za każdym razem w tej samej kolejności:
index.html - jedyny plik HTML w projekcie.<script type="module"> i prosi o main.js, a Vite podaje jej ten plik.import sprowadzają createApp z pakietu vue oraz komponent App.createApp(App) tworzy instancję aplikacji - na ekranie wciąż nic nie widać.app.use() dokładają moduły, jeśli projekt z nich korzysta.app.mount('#app') podłącza aplikację do pustego <div> w index.html.App wewnątrz tego elementu i system jest gotowy do pracy.Ta kolejność nie jest umowna - wynika z zależności. Nie zamontujesz aplikacji, której jeszcze nie powołałeś, i nie powołasz jej, zanim sprowadzisz
createApp. Jedyny krok, który w prostym projekcie może zniknąć, to punkt piąty - odpada, gdy nie podłączasz żadnych modułów. Cała reszta zawsze wygląda tak samo, w każdym projekcie Vue, jaki otworzysz.Zapamiętaj, @name:
index.html to platforma lądowania, main.js to sekwencja zapłonu, App.vue to rama stacji - a app.mount('#app') to jedno wywołanie, w którym system naprawdę rusza.