Korzystanie z automatów w Vue.js
Opublikowany: 2022-03-10Wraz z niedawnym wydaniem Vue 2.6 składnia używania slotów została uproszczona. Ta zmiana dotycząca slotów ponownie zainteresowała mnie odkryciem potencjalnej mocy slotów, aby zapewnić możliwość ponownego wykorzystania, nowe funkcje i lepszą czytelność naszych projektów opartych na Vue. Do czego naprawdę zdolne są automaty?
Jeśli jesteś nowy w Vue lub nie widziałeś zmian z wersji 2.6, czytaj dalej. Prawdopodobnie najlepszym źródłem do nauki o slotach jest własna dokumentacja Vue, ale postaram się opisać tutaj.
Czym są automaty?
Sloty to mechanizm dla komponentów Vue, który pozwala komponować komponenty w sposób inny niż ścisła relacja rodzic-dziecko. Gniazda umożliwiają umieszczanie treści w nowych miejscach lub tworzenie bardziej ogólnych elementów. Najlepszym sposobem na ich zrozumienie jest zobaczenie ich w akcji. Zacznijmy od prostego przykładu:
// frame.vue <template> <div class="frame"> <slot></slot> </div> </template>
Ten składnik ma opakowujący div
. Załóżmy, że div
jest po to, by stworzyć wokół treści ramę stylistyczną. Ten komponent może być używany ogólnie do owijania ramki wokół dowolnej treści. Zobaczmy, jak to wygląda, aby z niego korzystać. Komponent frame
odnosi się tutaj do komponentu, który właśnie stworzyliśmy powyżej.
// app.vue <template> <frame><img src="an-image.jpg"></frame> </template>
Zawartość znajdująca się między otwierającym i zamykającym znacznikiem frame
zostanie wstawiona do komponentu frame
, w którym znajduje się slot
, zastępując znaczniki slot
. To najbardziej podstawowy sposób na zrobienie tego. Możesz również określić domyślną zawartość, która ma trafić do slotu, po prostu wypełniając ją:
// frame.vue <template> <div class="frame"> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>
Więc teraz, jeśli zamiast tego użyjemy tego w ten sposób:
// app.vue <template> <frame /> </template>
Pojawi się domyślny tekst „To jest zawartość domyślna, jeśli nic nie zostanie tutaj określone”, ale jeśli użyjemy go tak, jak wcześniej, domyślny tekst zostanie zastąpiony przez tag img
.
Wiele/nazwanych slotów
Możesz dodać wiele gniazd do komponentu, ale jeśli to zrobisz, wszystkie oprócz jednego muszą mieć nazwę. Jeśli istnieje jeden bez nazwy, jest to gniazdo domyślne. Oto jak tworzysz wiele boksów:
// titled-frame.vue <template> <div class="frame"> <header><h2><slot name="header">Title</slot></h2></header> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>
Zachowaliśmy ten sam domyślny slot, ale tym razem dodaliśmy slot nazwany header
, w którym można wpisać tytuł. Używasz go w ten sposób:
// app.vue <template> <titled-frame> <template v-slot:header> <!-- The code below goes into the header slot --> My Image's Title </template> <!-- The code below goes into the default slot --> <img src="an-image.jpg"> </titled-frame> </template>
Tak jak poprzednio, jeśli chcemy dodać zawartość do domyślnego slotu, po prostu umieść ją bezpośrednio w komponencie titled-frame
. Aby dodać zawartość do nazwanego gniazda, musieliśmy jednak umieścić kod w tagu template
z dyrektywą v-slot
. Dodajesz dwukropek ( :
) po v-slot
, a następnie wpisujesz nazwę slotu, do którego chcesz przekazać zawartość. Zwróć uwagę, że v-slot
jest nowością w Vue 2.6, więc jeśli używasz starszej wersji, musisz przeczytać dokumentację o przestarzałej składni gniazda.
Ograniczone sloty
Jeszcze jedną rzeczą, którą musisz wiedzieć, jest to, że sloty mogą przekazywać dane/funkcje swoim dzieciom. Aby to zademonstrować, potrzebujemy zupełnie innego przykładowego komponentu ze slotami, który jest jeszcze bardziej wymyślny niż poprzedni: skopiujmy przykład z dokumentacji, tworząc komponent, który dostarcza dane o bieżącym użytkowniku do jego slotów:
// current-user.vue <template> <span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span> </template> <script> export default { data () { return { user: ... } } } </script>
Ten składnik ma właściwość o nazwie user
ze szczegółowymi informacjami o użytkowniku. Domyślnie komponent pokazuje nazwisko użytkownika, ale pamiętaj, że używa v-bind
do powiązania danych użytkownika z gniazdem. Dzięki temu możemy użyć tego komponentu, aby przekazać dane użytkownika swojemu potomkowi:
// app.vue <template> <current-user> <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template> </current-user> </template>
Aby uzyskać dostęp do danych przekazywanych do slotu, podajemy nazwę zmiennej scope z wartością dyrektywy v-slot
.
Oto kilka uwag do zrobienia:
- Określiliśmy nazwę
default
, ale nie musimy tego robić dla domyślnego slotu. Zamiast tego możemy po prostu użyćv-slot="slotProps"
. - Nie musisz używać
slotProps
jako nazwy. Możesz to nazwać jak chcesz. - Jeśli używasz tylko domyślnego slotu, możesz pominąć ten wewnętrzny tag
template
i umieścić dyrektywęv-slot
bezpośrednio w tagucurrent-user
. - Możesz użyć destrukturyzacji obiektów, aby utworzyć bezpośrednie odwołania do danych przedziału w zakresie, zamiast używać jednej nazwy zmiennej. Innymi słowy, możesz użyć
v-slot="{user}"
zamiastv-slot="slotProps"
, a następnie możesz użyćuser
bezpośrednio zamiastslotProps.user
.
Biorąc pod uwagę te notatki, powyższy przykład można przepisać w ten sposób:
// app.vue <template> <current-user v-slot="{user}"> {{ user.firstName }} </current-user> </template>
Jeszcze kilka rzeczy, o których należy pamiętać:
- Możesz powiązać więcej niż jedną wartość za pomocą dyrektyw
v-bind
. W tym przykładzie mogłem zrobić więcej niż tylkouser
. - Możesz również przekazywać funkcje do przedziałów w zakresie. Wiele bibliotek korzysta z tego, aby zapewnić elementy funkcjonalne wielokrotnego użytku, jak zobaczysz później.
-
v-slot
ma alias#
. Więc zamiast pisaćv-slot:header="data"
, możesz napisać#header="data"
. Możesz także po prostu określić#header
zamiastv-slot:header
, gdy nie używasz slotów z zakresem. Jeśli chodzi o domyślne sloty, musisz podać nazwędefault
, gdy używasz aliasu. Innymi słowy, musisz wpisać#default="data"
zamiast#="data"
.
Jest jeszcze kilka drobnych kwestii, o których możesz dowiedzieć się z dokumentacji, ale to powinno wystarczyć, aby zrozumieć, o czym mówimy w dalszej części tego artykułu.
Co możesz zrobić z automatami?
Automaty nie zostały zbudowane w jednym celu, a przynajmniej jeśli były, rozwinęły się daleko poza pierwotny zamiar bycia potężnym narzędziem do robienia wielu różnych rzeczy.
Wzory wielokrotnego użytku
Komponenty zawsze były projektowane tak, aby można je było ponownie wykorzystać, ale niektóre wzorce nie są praktyczne do wyegzekwowania za pomocą jednego „normalnego” komponentu, ponieważ liczba props
, których będziesz potrzebować, aby je dostosować, może być nadmierna lub będziesz musiał przepuszczać duże sekcje treści i potencjalnie inne komponenty przez props
. Szczeliny mogą być używane do objęcia „zewnętrznej” części wzoru i pozwalają innym HTML i/lub komponentom na umieszczenie wewnątrz nich w celu dostosowania „wewnętrznej” części, umożliwiając komponentowi ze szczelinami zdefiniowanie wzoru i komponentów wstrzykiwanych do sloty, aby były wyjątkowe.
W naszym pierwszym przykładzie zacznijmy od czegoś prostego: przycisku. Wyobraź sobie, że Ty i Twój zespół korzystacie z Bootstrap*. W przypadku Bootstrap, twoje przyciski są często powiązane z podstawową klasą `btn` i klasą określającą kolor, taką jak `btn-primary`. Możesz także dodać klasę rozmiaru, taką jak `btn-lg`.
* Nie zachęcam ani nie zniechęcam do robienia tego, po prostu potrzebowałem czegoś na swój przykład i jest to dość dobrze znane.
Załóżmy teraz, dla uproszczenia, że Twoja aplikacja/witryna zawsze używa btn-primary
i btn-lg
. Nie chcesz zawsze pisać wszystkich trzech klas na swoich przyciskach, a może nie ufasz nowicjuszowi, że pamięta o wykonaniu wszystkich trzech. W takim przypadku możesz utworzyć komponent, który automatycznie będzie zawierał wszystkie trzy klasy, ale jak zezwolić na dostosowywanie zawartości? prop
nie jest praktyczny, ponieważ znacznik button
może zawierać wszystkie rodzaje kodu HTML, więc powinniśmy użyć slotu.
<!-- my-button.vue --> <template> <button class="btn btn-primary btn-lg"> <slot>Click Me!</slot> </button> </template>
Teraz możemy go używać wszędzie z dowolną zawartością:

<!-- somewhere else, using my-button.vue --> <template> <my-button> <img src="/img/awesome-icon.jpg"> SMASH THIS BUTTON TO BECOME AWESOME FOR ONLY $500!!! </my-button> </template>
Oczywiście możesz wybrać coś znacznie większego niż przycisk. Pozostając przy Bootstrap, spójrzmy na modalny, a przynajmniej na część HTML; Nie będę wchodzić w funkcjonalność… jeszcze.
<!-- my-modal.vue --> <template> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <slot name="header"></slot> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <slot name="footer"></slot> </div> </div> </div> </div> </template>
Teraz użyjmy tego:
<!-- somewhere else, using my-modal.vue --> <template> <my-modal> <template #header><!-- using the shorthand for `v-slot` --> <h5>Awesome Interruption!</h5> </template> <template #body> <p>We interrupt your use of our application to let you know that this application is awesome and you should continue using it every day for the rest of your life!</p> </template> <template #footer> <em>Now back to your regularly scheduled app usage</em> </template> </my-modal> </template>
Powyższy typ przypadku użycia dla automatów jest oczywiście bardzo przydatny, ale może zrobić jeszcze więcej.
Ponowne wykorzystanie funkcjonalności
Komponenty Vue to nie tylko HTML i CSS. Są zbudowane z JavaScript, więc dotyczą również funkcjonalności. Sloty mogą być przydatne do jednorazowego tworzenia funkcjonalności i używania jej w wielu miejscach. Wróćmy do naszego przykładu modalnego i dodajmy funkcję, która zamyka modalny:
<!-- my-modal.vue --> <template> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <slot name="header"></slot> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <!-- using `v-bind` shorthand to pass the `closeModal` method to the component that will be in this slot --> <slot name="footer" :closeModal="closeModal"></slot> </div> </div> </div> </div> </template> <script> export default { //... methods: { closeModal () { // Do what needs to be done to close the modal... and maybe remove it from the DOM } } } </script>
Teraz, gdy używasz tego komponentu, możesz dodać przycisk do stopki, który może zamknąć modalny. Normalnie, w przypadku modu Bootstrap, możesz po prostu dodać data-dismiss="modal"
do przycisku, ale chcemy ukryć konkretne rzeczy Bootstrap z dala od komponentów, które będą umieszczane w tym składniku modalnym. Więc przekazujemy im funkcję, którą mogą wywołać, a oni wcale nie są mądrzejsi w zaangażowaniu Bootstrapa:
<!-- somewhere else, using my-modal.vue --> <template> <my-modal> <template #header><!-- using the shorthand for `v-slot` --> <h5>Awesome Interruption!</h5> </template> <template #body> <p>We interrupt your use of our application to let you know that this application is awesome and you should continue using it every day for the rest of your life!</p> </template> <!-- pull in `closeModal` and use it in a button's click handler --> <template #footer="{closeModal}"> <button @click="closeModal"> Take me back to the app so I can be awesome </button> </template> </my-modal> </template>
Komponenty bez renderowania
I na koniec, możesz wykorzystać to, co wiesz o korzystaniu z automatów, aby przekazać funkcjonalność wielokrotnego użytku i usunąć praktycznie cały kod HTML i po prostu korzystać z gniazd. Tym właśnie jest komponent bez renderowania: komponent, który zapewnia tylko funkcjonalność bez żadnego kodu HTML.
Tworzenie komponentów naprawdę bez renderowania może być trochę trudne, ponieważ będziesz musiał napisać funkcje render
zamiast używać szablonu, aby wyeliminować potrzebę elementu głównego, ale nie zawsze jest to konieczne. Rzućmy jednak okiem na prosty przykład, który pozwala nam najpierw użyć szablonu:
<template> <transition name="fade" v-bind="$attrs" v-on="$listeners"> <slot></slot> </transition> </template> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>
To dziwny przykład komponentu renderless, ponieważ nie zawiera on nawet żadnego JavaScriptu. Dzieje się tak głównie dlatego, że po prostu tworzymy wstępnie skonfigurowaną, wielokrotnego użytku wersję wbudowanej funkcji bez renderowania: transition
.
Tak, Vue ma wbudowane komponenty bez renderowania. Ten konkretny przykład pochodzi z artykułu Cristi Jora o przejściach wielokrotnego użytku i pokazuje prosty sposób tworzenia komponentu bez renderowania, który może ujednolicić przejścia używane w całej aplikacji. Artykuł Cristi jest o wiele bardziej szczegółowy i pokazuje bardziej zaawansowane odmiany przejść wielokrotnego użytku, więc polecam go sprawdzić.
W naszym drugim przykładzie utworzymy komponent, który obsługuje przełączanie tego, co jest wyświetlane podczas różnych stanów obietnicy: oczekująca, pomyślnie rozwiązana i nieudana. Jest to powszechny wzorzec i chociaż nie wymaga dużo kodu, może zamącić wiele twoich komponentów, jeśli logika nie zostanie wyciągnięta w celu ponownego użycia.
<!-- promised.vue --> <template> <span> <slot name="rejected" v-if="error" :error="error"></slot> <slot name="resolved" v-else-if="resolved" :data="data"></slot> <slot name="pending" v-else></slot> </span> </template> <script> export default { props: { promise: Promise }, data: () => ({ resolved: false, data: null, error: null }), watch: { promise: { handler (promise) { this.resolved = false this.error = null if (!promise) { this.data = null return } promise.then(data => { this.data = data this.resolved = true }) .catch(err => { this.error = err this.resolved = true }) }, immediate: true } } } </script>
Więc co tu się dzieje? Po pierwsze, zauważ, że otrzymujemy rekwizyt zwany promise
, który jest Promise
. W sekcji watch
obserwujemy zmiany w obietnicy, a gdy się ona zmieni (lub immediate
po utworzeniu komponentu dzięki właściwości direct), czyścimy stan, a then
wywołujemy i catch
obietnicę, aktualizując stan, gdy albo zakończy się pomyślnie, albo zawodzi.
Następnie w szablonie pokazujemy inny slot w zależności od stanu. Zauważ, że nie udało nam się utrzymać go naprawdę bez renderowania, ponieważ potrzebowaliśmy elementu głównego, aby użyć szablonu. Przekazujemy również data
i error
do odpowiednich zakresów gniazd.
A oto przykład użycia:
<template> <div> <promised :promise="somePromise"> <template #resolved="{ data }"> Resolved: {{ data }} </template> <template #rejected="{ error }"> Rejected: {{ error }} </template> <template #pending> Working on it... </template> </promised> </div> </template> ...
Przekazujemy jakąś obietnicę do komponentu somePromise
. Czekając na zakończenie, wyświetlamy „Pracujemy nad tym…” dzięki pending
slocie. Jeśli się powiedzie, wyświetlamy „Resolved:” i wartość rozdzielczości. Jeśli to się nie powiedzie, wyświetlamy „Odrzucono:” i błąd, który spowodował odrzucenie. Teraz nie musimy już śledzić stanu obietnicy w tym komponencie, ponieważ ta część jest wyciągana do własnego komponentu wielokrotnego użytku.
Co więc możemy zrobić z tym span
owiniętym wokół gniazd w promised.vue
? Aby go usunąć, musimy usunąć część template
i dodać funkcję render
do naszego komponentu:
render () { if (this.error) { return this.$scopedSlots['rejected']({error: this.error}) } if (this.resolved) { return this.$scopedSlots['resolved']({data: this.data}) } return this.$scopedSlots['pending']() }
Nie dzieje się tu nic trudnego. Używamy tylko niektórych bloków if
, aby znaleźć stan, a następnie zwracamy poprawną szczelinę w zakresie (poprzez this.$scopedSlots['SLOTNAME'](...)
) i przekazujemy odpowiednie dane do zakresu szczelin. Jeśli nie używasz szablonu, możesz pominąć używanie rozszerzenia pliku .vue
, wyciągając kod JavaScript z tagu script
i po prostu umieszczając go w pliku .js
. Powinno to dać bardzo niewielki wzrost wydajności podczas kompilowania tych plików Vue.
Ten przykład jest okrojoną i nieco podrasowaną wersją vue-promised, którą polecam skorzystać z powyższego przykładu, ponieważ obejmują one niektóre potencjalne pułapki. Istnieje również wiele innych świetnych przykładów komponentów bez renderowania. Baleada to cała biblioteka pełna komponentów bez renderowania, które zapewniają taką użyteczną funkcjonalność. Istnieje również vue-virtual-scroller do kontrolowania renderowania elementu listy na podstawie tego, co jest widoczne na ekranie lub PortalVue do „teleportacji” zawartości do zupełnie innych części DOM.
Wychodzę
Automaty Vue przenoszą rozwój oparty na komponentach na zupełnie nowy poziom i chociaż pokazałem wiele wspaniałych sposobów wykorzystania automatów, jest ich niezliczona ilość. Jaki świetny pomysł możesz wymyślić? Jak myślisz, w jaki sposób automaty mogą zostać ulepszone? Jeśli masz jakieś, koniecznie przedstaw swoje pomysły zespołowi Vue. Niech Bóg błogosławi i szczęśliwego kodowania.