Wprowadzenie
W świecie rozwoju aplikacji internetowych, Vue.js stał się jednym z najpopularniejszych frameworków do budowania interaktywnych interfejsów użytkownika. Jego prostota i elastyczność przyciągają programistów na każdym poziomie doświadczenia. Wraz z pojawieniem się Vue 3, wiele aspektów frameworka zostało udoskonalonych lub przemyślanych na nowo. W tym artykule skupimy się na jednym z kluczowych mechanizmów, mianowicie na v-model
.
Czym jest v-model?
Mechanizm v-model
w Vue odnosi się do dwukierunkowego wiązania danych (two way data binding), oznacza to, że gdy przykładowo wprowadzasz dane do jednego z elementów formularza, np. <input>
to będą one automatycznie aktualizowane w miejscu, gdzie ich używasz. Jeśli będą widoczne na stronie, to wpisywanie treści będzie na bieżąco aktualizowane. Cały mechanizm polega na tym aby kawałek stanu, do którego jest "podpięty" v-model
był zawsze aktualny. W kolejnych sekcjach poznasz szczegóły działania tego mechanizmu.
Działanie na natywnym elemencie
Zanim przejdziemy do używania dyrektywy v-model
w komponentach zacznijmy od zrozumienia z czego się składa.
Kod, który widzisz powyżej ma takie samo działanie, jak ten poniżej. Dlaczego zatem można to zapisać na dwa różne sposoby? Ano dlatego, że w programowaniu tak już jest, że twórcy takich mechanizmów starają się je usprawniać, a dodając odrobinę abstrakcji, stosując tzw. lukier składniowy (syntax sugar) zyskujemy sporo na czytelności.
Wytłumaczę krok po kroku co dzieje się w kodzie. Zmienna name
została przypisana do atrybutu value
, co oznacza, że jej wartość będzie widoczna w elemencie <input>
. Aby w Vue przypisać wartość dynamiczną musimy przed atrybutem zastosować dyrektywę v-bind:
lub jej skrócony zapis, samego znaku dwukropka :
. Chodzi tutaj o przypisanie wartości dynamicznej do atrybutu elementu HTML. Następnie zostaje dodany nasłuchiwacz na zdarzenie input
(zwróć uwagę, że mówimy tu o zdarzeniu, występuje tu jedynie zbieżność nazw między elementem HTML, a nazwą zdarzenia). W Vue, aby nasłuchiwać na zdarzenia generowane przez elementy HTML, można użyć dyrektywy v-on:
lub jej skrótowego zapisu @
. Następnie po niej, umieszczamy nazwę zdarzenia, w naszym przypadku v-on:input
lub @input
. Tu też pamiętaj, że nasłuchujemy na zdarzenie generowane przez elementy HTML.
Po określeniu zdarzenia, musimy jeszcze zdefiniować, co ma się wydarzyć po jego wystąpieniu. W przykładzie zdarzenie input
zostanie wyzwolone za każdym, gdy wartość atrybutu value
zostanie zmodyfikowana (czyli za każdym razem, gdy użytkownik wprowadzi zmianę w polu <input>
), a to, co do niego przypisujemy, to w zasadzie, to co chcemy, aby się wtedy wydarzyło. U nas jest to odwołanie do specjalnej zmiennej w Vue, $event
, która zawiera obiekt generowanego zdarzenia.
Aby dostać się do wprowadzonej wartości, odnosimy się do $event.target.value
, co wskazuje na aktualną wartość wpisaną w elemencie <input>
. Także jedyne co tutaj robimy, to przypisujemy do naszej zmiennej name
, aktualną wartość wpisaną w ten element, a że do atrybutu value
ją przypisaliśmy, to tworzy się dwukierunkowe wiązanie danych.
Działanie w komponencie
W przypadku, gdy chcesz utworzyć własny komponent i wykorzystać v-model
wygląda to nieco inaczej, lecz zasada jest taka sama. Załóżmy, że będzie to komponent właśnie takiego pola do wprowadzania imienia (specjalnie wybrałem ten sam przykład co przy elemencie natywnym, abyś mógł to dobrze zrozumieć, nie sugeruj się zatem użytecznością i ograniczeniami takiego komponentu). Tworzymy komponent <MyInput>
(w Vue, nazwy komponentów piszemy z użyciem PascalCase, czyli pierwszy znak każdego słowa jest pisany wielką literą), pomijając inne atrybuty jakie nasz komponent może przyjąć, przekazujemy mu tym razem :model-value
, jako wartość która będzie emitowana z naszego modelu oraz @update:model-value
jako nasłuchiwacz zdarzenia.
Z racji iż takie zdarzenie nie istnieje, to na nas, jako twórców takiego komponentu spoczywa obowiązek obsłużenia tego w prawidłowy sposób.
Przeanalizujmy ten kod. Dla osób, które nigdy nie miały do czynienia z Vue, słowem wstępu dodam, że template
służy do umieszczania struktury, natomiast script
do logiki, istnieje jeszcze blok style
dla styli natomiast nie o tym jest dzisiejszy artykuł.
A więc to co jest wymagane, aby v-model
w komponencie działał prawidłowo, to w template
, do stworzonego elementu HTML <input>
, tak jak w poprzedniej sekcji podłączamy jego wartość do value
, tym razem jest to modelValue
, który jest argumentem w naszym komponencie tzw. propsem.
Zanim przejdziemy do tego co dzieje się w zdarzeniu, spójrz na chwilę do bloku logicznego, co tam się dzieje? Nic strasznego, defineProps
to funkcja, która definiuje jakie właściwości (propsy) przyjmuje nasz komponent, dzięki temu wie, że taką właściwość może przyjąć, a defineEmits
, to z kolei funkcja do definiowania zdarzeń, które nasz komponent może wywołać (najlepiej wyobraź to sobie w ten sposób, że skoro wyemitujesz jakieś zdarzenie, to dajesz znać do komponentu nadrzędnego, który może na to zdarzenie zareagować.
Zatem wracając do zdarzenia w elemencie, tym razem odnosimy się do metody $emit
(dostępna w instancji Vue), która oczekuje od nas, jakie zdarzenie ma zostać wyemitowane oraz co ma zostać mu przekazane w momencie jego zaistnienia (w naszym przypadku, wpisania czegoś). Czy robi się nieco jaśniej? Jeśli nie, to jeszcze raz: w momencie zaistnienia zdarzenia, nasz komponent wyemituje zdarzenie, które jest nasłuchiwane wyżej przez @update:model-value
, do którego z kolei, właśnie ta przekazana niżej wartość z $event.target.value
wpadnie, jako newValue
i zostanie przypisana do name
.
Jeżeli zadajesz sobie pytanie, dlaczego w takim razie nie użyć w komponencie v-model
zamiast nasłuchiwać na zdarzenie oraz przekazywać propsa? to jest duża szansa, że zrozumiałeś całą koncepcję. Oczywiście, że w tym przypadku będziemy chcieli skorzystać z takiego zapisu:
Jest to tożsame z tym, co opisałem wcześniej: nasz v-model
to lukier składniowy zastępujący w przypadku komponentu :model-value
, jako wartość oraz @update:model-value
, jako nasłuchiwacz. Musisz pamiętać, że w przypadku komponentu v-model
domyślnie odnosi się do wartości modelValue
oraz zdarzenia update:modelValue
. Jeśli chciałbyś użyć innych nazw dla propsów, musisz albo jawnie przypisać wartość i nasłuchiwacz, albo skorzystać z multiple v-model
, co zostało opisane w kolejnej sekcji.
Multiple v-model
Jak wspomniałem w poprzedniej sekcji, możesz chcieć użyć innej nazwy dla v-modelowanej wartości lub też mieć ich kilka, aby dało się je rozróżnić, przekazując tą wartość do v-model
umieszczamy nazwę, którą chcemy, np. v-model:name
, wtedy podczas definiowania propsów, przekażemy name
, a podczas definiowania zdarzeń update:name
i cała zasada, pozostaje taka sama. Chciałem o tym wspomnieć dlatego, że pozwoli Ci to nieco rozjaśnić co dokładnie miałem na myśli, mówiąc o domyślnych nazwach w przypadku v-model
bez określenia jego argumentu.
Domyślną nazwą wcześniej, było modelValue
dla wartości oraz update:modelValue
dla zdarzenia, stąd też nie ma potrzeby zapisywania tego w taki sposób v-model:modelValue
, a wystarczy samo v-model
.
Teraz korzystając z tych możliwości możemy podpiąć pod komponent kilka v-modelowych wartości, np. tworząc podwójny input do wprowadzenia imienia oraz wieku:
Modyfikatory
W wersji trzeciej Vue, mamy możliwość korzystania z modyfikatorów w połączeniu z v-model
, które znacznie ułatwiają pracę. Zaraz opiszę, co dokładnie robią.
Modyfikatory wbudowane
lazy
- ten modyfikator działa podobnie do debouncingu (podobnie, nie znaczy tak samo), a to co robi to zmienia reakcje synchronizacji danych ze zdarzeniainput
na zdarzeniechange
. Może to być przydatne, gdy np. nie chcesz aktualizować danych po każdym wpisanym znaku.number
- jest używany głównie do konwersji wartości wprowadzonej przez użytkownika na typ liczbowy, gdy wartość jest emitowana. Nie blokuje ona jednak wprowadzania innych znaków niż cyfry w polu input. Założenie polega na tym, że wpisanie"12"
(string) zostanie automatycznie przekonwertowane na12
(number).trim
- bardzo przydatny modyfikator przy pracy z formularzami, pozwala na usuwanie białych znaków z początku oraz końca wprowadzanej treści.
Modyfikatory własne
No dobrze, zatem załóżmy że potrzebujesz konkretnego modyfikatora, zaimplementowanego przez siebie i chcesz go zastosować w swoim komponencie. Nic prostszego, Vue 3 umożliwia nam taką opcję. Tym razem, najpierw pokażę użycie, a zaraz jak wygląda tego implementacja:
To co widzisz, to w sumie żadna nowość, używamy v-model
jak dotychczas, lecz z dodanym po kropce modyfikatorem, i jest to modyfikator, który sami stworzymy, zatem do dzieła:
Analizując powyższy kod, jak zapewne zauważyłeś w zdarzeniu input
nie odwołujemy się już do $emit
, a to dlatego, że przypisaliśmy definiowanie emitów do zmiennej emit
, a samo zdarzenie ma przekazaną funkcję emitModelValue
(nazwa jest dowolna, oczywiście zachowując ogólne zasady nazewnictwa), która zostanie wywołana w trakcie wpisywania. Przypisujmy również do zmiennej props
zdefiniowane właściwości komponentu (często będziesz tak robić), aby w tym przykładzie pokazać że przekazany przez nas modyfikator uppercase
znajdzie się w propsie modelModifiers
(tam będą domyślnie dostępne wszystkie modyfikatory).
Sama funkcja odpowiada za emitowanie wprowadzanej wartości. Zanim to jednak nastąpi, najpierw modyfikujemy wartość zgodnie z naszymi potrzebami. W tym konkretnym przypadku tworzymy wewnątrz funkcji lokalną zmienną o nazwie "value". Do tej zmiennej przypisujemy wprowadzaną wartość.
Jeżeli używamy modyfikatora na "v-model", musimy przeprowadzić dodatkową weryfikację. Sprawdzamy, czy "props.modelModifiers.uppercase" jest ustawione na "true". Ale równie ważne jest, by upewnić się, czy "props.modelModifiers" w ogóle istnieje. Domyślnie, jeśli nie skonfigurowaliśmy tego sami, możemy napotkać błąd. Taki obiekt jest tworzony tylko wtedy, gdy użyty został choć jeden modyfikator.
Jeśli nasze warunki są spełnione, modyfikujemy wartość "value", zamieniając ją na wielkie litery. Po tej modyfikacji emitujemy wartość, podobnie jak to robiłaby funkcja bez użytego modyfikatora.
Podsumowanie
Podsumowując: pamiętaj, że zrozumienie tej koncepcji jest niezbędne przy pracy z Vue, więc jeśli nadal nie jest dla Ciebie jasne, jak działa ten mechanizm, to zachęcam do przede wszystko praktykowania, możesz zawsze wrócić do tego artykułu i przeczytać go ponownie, a jeśli masz jakieś pytania, to śmiało korzystaj z sekcji komentarzy pod artykułem, a z przyjemnością pomożemy. Niezależnie od tego, czy jesteś początkującym w świecie programowania, czy już bardziej doświadczonym deweloperem, zawsze warto inwestować w naukę i poszerzać swoją wiedzę.