W poprzedniej lekcji podpiąłeś panel misji do zmiennych: nazwa załogi, notatki, checkboxy systemów, lista modułów. Formularz działa, ale sygnał, który z niego wychodzi, jest surowy. Dyżurny Centrum Kontroli zgłasza trzy usterki. Po pierwsze, dziennik telemetrii zapisuje nową wartość po każdym naciśnięciu klawisza, więc jedno słowo zostawia w nim kilkanaście wpisów. Po drugie, pole z liczbą kredytów oddaje tekst, a nie liczbę, więc każde dodawanie skleja cyfry zamiast je sumować. Po trzecie, operator wkleja nazwę modułu ze schowka razem ze spacją na końcu i porównanie z katalogiem stacji przestaje trafiać.
Żadnej z tych usterek nie musisz naprawiać ręcznie, @name. Tak jak do zdarzeń dopisywałeś
.prevent czy .stop, tak do v-model możesz dopisać modyfikator: kropkę i nazwę tuż za nazwą dyrektywy. Modyfikator nie zmienia tego, z czym pole jest związane - zmienia sposób, w jaki wartość trafia do zmiennej. Vue udostępnia trzy: .lazy, .number i .trim. Każdy z nich naprawia dokładnie jedną z usterek dyżurnego.Zacznijmy od pierwszej usterki, bo dotyczy czasu, a nie treści. Pole tekstowe z
v-model nasłuchuje przeglądarkowego zdarzenia input, które przeglądarka wysyła po każdym pojedynczym znaku. Dla pola z podglądem na żywo to idealne zachowanie - operator pisze, panel od razu pokazuje efekt. Dla dziennika telemetrii albo kosztownego przeliczenia trajektorii to marnotrawstwo: interesuje nas jedna, gotowa wartość, a nie kilkanaście stanów pośrednich.Przeglądarka ma osobne zdarzenie na taką okazję:
change. W polu tekstowym wysyła je dopiero wtedy, gdy zmieniona wartość zostaje zatwierdzona - czyli gdy opuścisz pole (zdarzenie blur) albo naciśniesz Enter. Modyfikator .lazy robi tylko jedną rzecz: przełącza nasłuch v-model ze zdarzenia input na change. Poniżej dwa identycznie zbudowane kanały odczytu, różniące się wyłącznie tym modyfikatorem.1<template>
2 <label>Live (input)</label>
3 <input v-model="liveSignal" />
4 <p>Odczyt: {{ liveSignal }}</p>
5
6 <label>Lazy (change)</label>
7 <input v-model.lazy="committedSignal" />
8 <p>Odczyt: {{ committedSignal }}</p>
9</template>
10
11<script setup>
12import { ref } from 'vue'
13
14const liveSignal = ref('')
15const committedSignal = ref('')
16</script>Wpisz coś w pierwsze pole - akapit pod nim zmienia się litera po literze. Wpisz to samo w drugie - akapit stoi pusty, dopóki nie klikniesz obok albo nie naciśniesz Enter. I teraz najważniejsze: co się nie zmieniło. Wiązanie nadal jest dwukierunkowe, zmienna
committedSignal nadal jest zwykłym tekstem, a zapis w szablonie różni się o pięć znaków. Przesunął się wyłącznie moment aktualizacji.Warto od razu wykluczyć trzy nieporozumienia, bo
.lazy bywa mylony z rzeczami, którymi nie jest. To nie jest odliczanie czasu - nic nie dzieje się co 500 milisekund. Opóźnienie liczone zegarem to debounce i w Vue robisz je samodzielnie, watcherem z setTimeout, tak jak w module o systemach monitorowania. To nie jest też czekanie na wysłanie formularza - wartość ląduje w zmiennej, gdy tylko opuścisz pole, na długo przed kliknięciem przycisku. I na koniec: aktualizacja wciąż jest automatyczna, niczego nie musisz przepisywać ręcznie.Druga usterka jest podstępniejsza, bo panel wygląda poprawnie do chwili pierwszego działania arytmetycznego. Pole formularza w przeglądarce zawsze oddaje tekst - także wtedy, gdy wpisujesz w nie same cyfry. Jeśli więc przypiszesz je do zmiennej i dodasz liczbę, JavaScript nie policzy sumy, tylko sklei dwa napisy. Zobacz to na polu z kredytami misji, na razie bez żadnego modyfikatora.
1<template>
2 <label>Kredyty</label>
3 <input v-model="budget" />
4
5 <p>Typ: {{ typeof budget }}</p>
6 <p>budget + 10 = {{ budget + 10 }}</p>
7</template>
8
9<script setup>
10import { ref } from 'vue'
11
12const budget = ref('')
13</script>Wpisz 1500. Pierwszy akapit pokazuje
string, a drugi - zamiast 1510 - napis złożony z wpisanej wartości i doklejonej dziesiątki. To nie jest błąd Vue ani błąd przeglądarki, tylko naturalna konsekwencja tego, że pole formularza przechowuje znaki. Dopóki wartość służy wyłącznie do wyświetlenia, nikt tego nie zauważy. Kłopot zaczyna się przy sumowaniu budżetów, porównywaniu limitów i sortowaniu modułów po koszcie.Lekarstwem jest modyfikator
.number. Vue przepuszcza wtedy wpisaną wartość przez konwersję na liczbę - działa to tak jak parseFloat: z tekstu wyciągana jest liczba, a jeśli konwersja się nie uda, do zmiennej trafia niezmieniony oryginał. To bardzo ważne rozróżnienie, @name: .number konwertuje, ale nie waliduje. Nie odrzuci wpisu, nie zapali czerwonej lampki i nie zablokuje formularza - jeśli treści nie da się zamienić na liczbę, po prostu zostawi tekst takim, jakim był. Sprawdzanie poprawności to osobna praca, którą wykonasz w następnej lekcji.1<template>
2 <label>Kredyty</label>
3 <input v-model.number="budget" type="number" />
4
5 <p>Typ: {{ typeof budget }}</p>
6 <p>budget + 10 = {{ budget + 10 }}</p>
7</template>
8
9<script setup>
10import { ref } from 'vue'
11
12const budget = ref(0)
13</script>Ten sam szablon, jeden dopisany modyfikator - i pierwszy akapit pokazuje teraz
number, a drugi prawdziwą sumę. Zwróć uwagę, czego .number nie robi. Nie pilnuje, żeby dało się wpisać wyłącznie cyfry - to zadanie atrybutu type="number", który należy do przeglądarki i to on decyduje, co w ogóle wolno wystukać w polu. Nie formatuje też liczby do postaci z separatorami tysięcy; taki zapis dla wygody czytającego robi się osobno, przy wyświetlaniu, metodą toLocaleString. W samym polu wartość pozostaje surową liczbą.Skoro atrybut
type="number" już się pojawił, jedno uczciwe zastrzeżenie. Vue traktuje takie pole jako numeryczne z założenia i próbuje konwersji nawet bez modyfikatora, więc w tym konkretnym przykładzie .number niczego nie zmieni. Mimo to zalecam pisać go zawsze i wprost. Po pierwsze, intencja jest wtedy widoczna w szablonie i nie zależy od tego, jaki type ktoś kiedyś podmieni. Po drugie, ten sam zapis zadziała także na zwykłym polu tekstowym. Kanoniczna postać, którą warto mieć w palcach, wygląda dokładnie tak jak w bloku powyżej: najpierw <input, potem v-model.number= z nazwą zmiennej w cudzysłowie, a na końcu type="number".Trzecia usterka bierze się z ludzkiego odruchu. Operator kopiuje nazwę modułu z raportu i wraz z nią zabiera spację na końcu. Na ekranie nie widać niczego niepokojącego, ale w zmiennej siedzi znak, który psuje wszystko, co porównuje teksty: wyszukiwanie w katalogu stacji nie znajduje modułu, a naiwne sprawdzenie "czy pole jest puste" przepuszcza wpis złożony z samych spacji. Modyfikator
.trim usuwa białe znaki z początku i z końca wartości, zanim ta trafi do zmiennej.1<template>
2 <label>Raw</label>
3 <input v-model="rawName" />
4 <p>chars: {{ rawName.length }}</p>
5
6 <label>Trimmed</label>
7 <input v-model.trim="missionName" />
8 <p>chars: {{ missionName.length }}</p>
9</template>
10
11<script setup>
12import { ref } from 'vue'
13
14const rawName = ref('')
15const missionName = ref('')
16</script>Wpisz w oba pola nazwę ze spacją na początku i na końcu. Licznik znaków pod pierwszym polem policzy spacje, licznik pod drugim - nie. Teraz trzy granice, o których łatwo zapomnieć.
.trim nie skraca tekstu do zadanej długości - nie ma żadnego limitu znaków, przycinane są wyłącznie krawędzie. Nie usuwa też wszystkich spacji: nazwa "Oxygen Alpha" zachowa swoją spację w środku, bo modyfikator nie rusza wnętrza napisu. I nie zmienia wielkości liter - zamiana na małe litery to toLowerCase, zupełnie inne narzędzie. Co równie ważne, modyfikator pracuje na wartości wędrującej do zmiennej, a nie na tym, co fizycznie stukasz w pole - w trakcie pisania spacja jest w polu widoczna, tyle że zmienna jej nie dostaje.Panel rejestracji misji potrzebuje wszystkich trzech poprawek naraz i nie musisz wybierać jednej. Modyfikatory łączy się w łańcuch: kolejne nazwy dopisujesz jeden za drugim, każdą poprzedzoną kropką, bez spacji i bez przecinków. Zapis
v-model.lazy.trim="text" jest w pełni poprawny - a przecinek między nazwami byłby dla Vue częścią jednej, nieznanej nazwy modyfikatora i po prostu nie zadziałałby.Vue nie wymusza kolejności, więc
v-model.trim.lazy znaczy dokładnie to samo co v-model.lazy.trim. Skoro wybór należy do Ciebie, polecam trzymać stałą konwencję: najpierw modyfikator opisujący, czym jest wartość (.number albo .trim), potem ten opisujący, kiedy trafia do zmiennej (.lazy). Stąd v-model.number.lazy - czyta się to jak zdanie: liczba, zatwierdzana po opuszczeniu pola.1<template>
2 <form @submit.prevent="registerMission">
3 <label>Nazwa misji</label>
4 <input v-model.trim.lazy="missionName" />
5
6 <label>Kredyty</label>
7 <input v-model.number.lazy="budget" type="number" />
8
9 <button type="submit">Zarejestruj</button>
10 </form>
11
12 <p>Panel: {{ missionName }} / {{ budget }}</p>
13</template>
14
15<script setup>
16import { ref } from 'vue'
17
18const missionName = ref('')
19const budget = ref(0)
20
21function registerMission() {
22 console.log('Mission registered', missionName.value, budget.value)
23}
24</script>Wypełnij oba pola i przejdź między nimi tabulatorem, nie dotykając przycisku. Akapit pod formularzem pokazuje już przyciętą nazwę i liczbowy budżet - dowód, że
.lazy czeka na opuszczenie pola, a nie na wysłanie formularza. Kliknięcie przycisku najpierw zabiera fokus z pola, więc do funkcji registerMission zawsze trafiają wartości aktualne. Poza tym w tym bloku nie ma nic nowego: @submit.prevent znasz z lekcji o modyfikatorach zdarzeń, a ref i v-model towarzyszą Ci od początku modułu. Doszły tylko kropki z modyfikatorami.Na koniec przestroga, bo modyfikatory bywają dodawane odruchowo. Nie dawaj
.lazy polu, które filtruje listę modułów na żywo - operator straci podgląd wyników w trakcie pisania i uzna panel za zepsuty. Uważaj z .number przy oznaczeniach, które tylko wyglądają jak liczby: numer sektora zapisany jako 007 po konwersji zostanie siódemką i przestanie pasować do katalogu. Z .trim przy nazwach, adresach i identyfikatorach kłopotów praktycznie nie ma i śmiało możesz go stosować domyślnie.Pamiętaj: modyfikator
v-model to pokrętło kalibracji przy panelu - sygnał płynie tym samym kablem, ale do komputera misji dociera już czysty, we właściwym typie i we właściwej chwili.