Co nowego w Vue 3?
Opublikowany: 2022-03-10Wraz z wydaniem Vue 3 programiści muszą dokonać uaktualnienia z Vue 2, ponieważ zawiera on garść nowych funkcji, które są bardzo pomocne w tworzeniu łatwych do odczytania i konserwacji komponentów oraz ulepszonych sposobów struktury naszej aplikacji w Vue. W tym artykule przyjrzymy się niektórym z tych funkcji.
Pod koniec tego samouczka czytelnicy będą;
- Dowiedz się, jak
provide / inject
i jak z niego korzystać. - Masz podstawową wiedzę na temat Teleportu i tego, jak z niego korzystać.
- Dowiedz się o fragmentach i o tym, jak z nich korzystać.
- Dowiedz się o zmianach wprowadzonych w Global Vue API.
- Dowiedz się o zmianach wprowadzonych w interfejsie Events API.
Ten artykuł jest skierowany do tych, którzy dobrze rozumieją Vue 2.x. Cały kod użyty w tym przykładzie można znaleźć w serwisie GitHub.
provide / inject
W Vue 2.x mieliśmy props
, które ułatwiały przekazywanie danych (ciągów, tablic, obiektów itp.) z komponentu nadrzędnego bezpośrednio do komponentu potomnego. Jednak podczas projektowania często natrafialiśmy na sytuacje, w których musieliśmy przekazać dane z komponentu nadrzędnego do głęboko zagnieżdżonego komponentu, co było trudniejsze do wykonania przy użyciu props
. Spowodowało to użycie Vuex Store, Event Hub, a czasem przekazywanie danych przez głęboko zagnieżdżone komponenty. Spójrzmy na prostą aplikację;
Ważne jest, aby pamiętać, że Vue 2.2.0 był również dostarczany z funkcją provider provide / inject
, której nie zalecano w ogólnym kodzie aplikacji.
# parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { color: "", colors: ["red", "blue", "green"], }; }, }; </script>
# childComponent.vue <template> <div class="hello"> <h1>{{ msg }}</h1> <color-selector :color="color"></color-selector> </div> </template> <script> import colorSelector from "@/components/colorComponent.vue"; export default { name: "HelloWorld", components: { colorSelector, }, props: { msg: String, color: String, }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
# colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { props: { color: String, }, }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>
Tutaj mamy stronę docelową z rozwijaną listą kolorów i przekazujemy wybrany color
do childComponent.vue
jako rekwizyt. Ten komponent potomny ma również właściwość msg
, która akceptuje tekst do wyświetlenia w sekcji szablonu. Wreszcie, ten składnik ma składnik potomny ( colorComponent.vue
), który przyjmuje podpowiedź color
z komponentu nadrzędnego, która jest używana do określania klasy tekstu w tym komponencie. To jest przykład przekazywania danych przez wszystkie komponenty.
Ale dzięki Vue 3 możemy to zrobić w czystszy i krótszy sposób, używając nowej pary Provide i Inject. Jak sama nazwa wskazuje, używamy provide
jako funkcji lub obiektu, aby udostępnić dane z komponentu nadrzędnego dowolnemu jego zagnieżdżonemu komponentowi, niezależnie od tego, jak głęboko zagnieżdżony jest taki komponent. Korzystamy z formularza obiektu podczas przekazywania wartości zakodowanych na stałe, aby provide
w ten sposób;
# parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { colors: ["red", "blue", "green"], }; }, provide: { color: 'blue' } }; </script>
Ale w przypadkach, w których musisz przekazać właściwość instancji komponentu, aby provide
, używamy trybu funkcji, więc jest to możliwe;
# parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "blue", colors: ["red", "blue", "green"], }; }, provide() { return { color: this.selectedColor, }; }, }; </script>
Ponieważ nie potrzebujemy właściwości color
w childComponent.vue
i colorComponent.vue
, pozbywamy się ich. Dobrą rzeczą w użyciu provide
jest to, że komponent nadrzędny nie musi wiedzieć, który komponent potrzebuje właściwości, którą dostarcza.
Aby skorzystać z tego w komponencie, który w tym przypadku tego potrzebuje, colorComponent.vue
robimy to;
# colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>
Tutaj używamy inject
, który pobiera tablicę wymaganych zmiennych, których potrzebuje komponent. W tym przypadku potrzebujemy tylko właściwości color
, więc tylko ją przekazujemy. Następnie możemy użyć color
w taki sam sposób, w jaki używamy go podczas korzystania z rekwizytów.
Możemy zauważyć, że jeśli spróbujemy wybrać nowy kolor za pomocą listy rozwijanej, kolor nie zostanie zaktualizowany w colorComponent.vue
, a to dlatego, że domyślnie właściwości w provider nie provide
reaktywne. Aby to naprawić, korzystamy z metody computed
.
# parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; import { computed } from "vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "", todos: ["Feed a cat", "Buy tickets"], colors: ["red", "blue", "green"], }; }, provide() { return { color: computed(() => this.selectedColor), }; }, }; </script>
Tutaj importujemy computed
i przekazujemy selectedColor
kolor, aby mógł być reaktywny i aktualizowany, gdy użytkownik wybierze inny kolor. Kiedy przekazujesz zmienną do obliczonej metody, zwraca ona obiekt, który ma value
. Ta właściwość przechowuje wartość Twojej zmiennej, więc w tym przykładzie musielibyśmy zaktualizować colorComponent.vue
, aby wyglądał tak;
# colorComponent.vue <template> <p :class="[color.value]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>
Tutaj zmieniamy color
na color.value
, aby przedstawić zmianę po wprowadzeniu color
do reaktywności przy użyciu metody computed
. W tym momencie class
tekstu w tym komponencie będzie się zmieniać zawsze, gdy w komponencie nadrzędnym zmieni się selectedColor
.
teleport
Istnieją przypadki, w których tworzymy komponenty i umieszczamy je w jednej części naszej aplikacji ze względu na logikę, z której korzysta aplikacja, ale mają być wyświetlane w innej części naszej aplikacji. Typowym przykładem może być modalne lub wyskakujące okienko, które ma wyświetlać i zakrywać cały ekran. Chociaż możemy stworzyć obejście tego problemu za pomocą właściwości position
CSS na takich elementach, z Vue 3 możemy to zrobić również za pomocą Teleportu.
Teleport umożliwia nam wyjęcie komponentu z jego oryginalnej pozycji w dokumencie, z domyślnego kontenera #app
, w którym są opakowane aplikacje Vue i przeniesienie go do dowolnego istniejącego elementu na stronie, z której jest używany. Dobrym przykładem może być użycie Teleportu do przeniesienia komponentu nagłówka z wnętrza div #app
do header
. Ważne jest, aby pamiętać, że możesz teleportować tylko do elementów, które istnieją poza Vue DOM.


Komponent Teleport akceptuje dwie rekwizyty, które określają zachowanie tego komponentu i są one;
-
to
Ta właściwość przyjmuje nazwę klasy, identyfikator, element lub atrybut data-*. Możemy również uczynić tę wartość dynamiczną, przekazując:to
prop w przeciwieństwieto
dynamicznego zmieniania elementu Teleport. -
:disabled
Ten rekwizyt przyjmuje wartościBoolean
i może być używany do przełączania funkcji Teleportacji na elemencie lub komponencie. Może to być przydatne do dynamicznej zmiany pozycji elementu.
Idealny przykład użycia Teleportu wygląda tak;
# index.html** <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <title> <%= htmlWebpackPlugin.options.title %> </title> </head> <!-- add container to teleport to --> <header class="header"></header> <body> <noscript> <strong >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript> <div></div> <!-- built files will be auto injected --> </body> </html>
W domyślnym pliku index.html
w Twojej aplikacji Vue dodajemy element header
, ponieważ chcemy teleportować nasz komponent nagłówka do tego miejsca w naszej aplikacji. Dodaliśmy również klasę do tego elementu w celu stylizacji i łatwego odwoływania się w naszym komponencie Teleport.
# Header.vue** <template> <teleport to="header"> <h1 class="logo">Vue 3 </h1> <nav> <router-link to="/">Home</router-link> </nav> </teleport> </template> <script> export default { name: "app-header", }; </script> <style> .header { display: flex; align-items: center; justify-content: center; } .logo { margin-right: 20px; } </style>
Tutaj tworzymy komponent nagłówka i dodajemy logo z linkiem do strony głównej w naszej aplikacji. Dodajemy również komponent Teleport i przypisujemy to
prop wartość header
, ponieważ chcemy, aby ten komponent renderował się wewnątrz tego elementu. Na koniec importujemy ten komponent do naszej aplikacji;
# App.vue <template> <router-view /> <app-header></app-header> </template> <script> import appHeader from "@/components/Header.vue"; export default { components: { appHeader, }, }; </script>
W tym pliku importujemy komponent nagłówka i umieszczamy go w szablonie, aby był widoczny w naszej aplikacji.
Teraz, gdy przyjrzymy się elementowi naszej aplikacji, zauważymy, że nasz składnik nagłówka znajduje się wewnątrz elementu header
;

Paprochy
W Vue 2.x niemożliwe było posiadanie wielu elementów głównych w template
pliku, a jako obejście, programiści zaczęli zawijać wszystkie elementy w element nadrzędny. Chociaż nie wygląda to na poważny problem, istnieją przypadki, w których programiści chcą renderować komponent bez kontenera owijającego takie elementy, ale muszą to zadowolić.
Wraz z Vue 3 wprowadzono nową funkcję o nazwie Fragmenty, która umożliwia programistom posiadanie wielu elementów w ich głównym pliku szablonu. Tak więc w Vue 2.x tak wyglądałby komponent kontenera pól wejściowych;
# inputComponent.vue <template> <div> <label :for="label">label</label> <input :type="type" : :name="label" /> </div> </template> <script> export default { name: "inputField", props: { label: { type: String, required: true, }, type: { type: String, required: true, }, }, }; </script> <style></style>
Tutaj mamy prosty komponent formularza, który akceptuje dwa atrybuty, label
i type
, a sekcja szablonu tego komponentu jest opakowana w div. Niekoniecznie jest to problem, ale jeśli chcesz, aby etykieta i pole wejściowe znajdowały się bezpośrednio w elemencie form
. Dzięki Vue 3 programiści mogą łatwo przepisać ten komponent, aby wyglądał tak;
# inputComponent.vue <template class="testingss"> <label :for="label">{{ label }}</label> <input :type="type" : :name="label" /> </template>
Z pojedynczym węzłem głównym atrybuty są zawsze przypisywane do węzła głównego i są one również znane jako Atrybuty Non-Prop . Są to zdarzenia lub atrybuty przekazywane do komponentu, które nie mają odpowiednich właściwości zdefiniowanych w props
lub emits
. Przykładami takich atrybutów są class
i id
. Wymagane jest jednak jednoznaczne zdefiniowanie, do którego z elementów w komponencie węzła wielokorzeniowego należy przypisać.
Oto, co to oznacza przy użyciu inputComponent.vue
z góry;
- Dodając
class
do tego komponentu w komponencie nadrzędnym, należy określić, do którego komponentu zostanie przypisana taclass
, w przeciwnym razie atrybut nie będzie działał.
<template> <div class="home"> <div> <input-component class="awesome__class" label="name" type="text" ></input-component> </div> </div> </template> <style> .awesome__class { border: 1px solid red; } </style>
Kiedy robisz coś takiego bez określenia, gdzie atrybuty powinny być przypisane, otrzymasz to ostrzeżenie w swojej konsoli;

A border
nie ma wpływu na komponent;

- Aby to naprawić, dodaj
v-bind="$attrs"
na elemencie, do którego chcesz dystrybuować takie atrybuty;
<template> <label :for="label" v-bind="$attrs">{{ label }}</label> <input :type="type" : :name="label" /> </template>
Tutaj mówimy Vue, że chcemy, aby atrybuty były dystrybuowane do elementu label
, co oznacza, że chcemy, aby została do niego zastosowana awesome__class
. Teraz, jeśli sprawdzimy nasz element w przeglądarce, zobaczymy, że klasa została teraz dodana do label
, a zatem wokół etykiety znajduje się ramka.

Globalny interfejs API
Nierzadko można było zobaczyć Vue.component
lub Vue.use
w pliku main.js
aplikacji Vue. Tego typu metody są znane jako Globalne API i jest ich sporo w Vue 2.x. Jednym z wyzwań tej metody jest to, że uniemożliwia wyizolowanie niektórych funkcji do jednej instancji Twojej aplikacji (jeśli masz więcej niż jedną instancję w swojej aplikacji) bez wpływu na inne aplikacje, ponieważ wszystkie są zamontowane na Vue. To mam na myśli;
Vue.directive('focus', { inserted: el => el.focus() }) Vue.mixin({ /* ... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })
W przypadku powyższego kodu nie można stwierdzić, że dyrektywa Vue jest powiązana z app1
i Mixin z app2
, ale zamiast tego obie są dostępne w obu aplikacjach.
Vue 3 jest dostarczany z nowym Global API, próbując rozwiązać tego typu problemy poprzez wprowadzenie createApp
. Ta metoda zwraca nowe wystąpienie aplikacji Vue. Wystąpienie aplikacji uwidacznia podzbiór bieżących globalnych interfejsów API. Dzięki temu wszystkie interfejsy API (komponent, mixin, dyrektywa, użycie itp.), które mutują Vue
z Vue 2.x, zostaną teraz przeniesione do poszczególnych instancji aplikacji, a teraz każda instancja Twojej aplikacji Vue może mieć funkcjonalności, które są unikalne dla je bez wpływu na inne istniejące aplikacje.
Teraz powyższy kod można przepisać jako;
const app1 = createApp({}) const app2 = createApp({}) app1.directive('focus', { inserted: el => el.focus() }) app2.mixin({ /* ... */ })
Możliwe jest jednak tworzenie funkcji, które chcesz udostępniać wszystkim swoim aplikacjom i można to zrobić za pomocą funkcji fabrycznej.
Wydarzenia API
Jednym z najczęstszych sposobów, w jakie programiści zaadoptowali do przekazywania danych między komponentami, które nie mają relacji rodzic-dziecko, poza korzystaniem ze sklepu Vuex, jest użycie magistrali zdarzeń. Jednym z powodów, dla których ta metoda jest powszechna, jest łatwość jej rozpoczęcia;
# eventBus.js const eventBus = new Vue() export default eventBus;
Następnie następną rzeczą byłoby zaimportowanie tego pliku do main.js
, aby był dostępny globalnie w naszej aplikacji lub zaimportowanie go w plikach, których potrzebujesz;
# main.js import eventBus from 'eventBus' Vue.prototype.$eventBus = eventBus
Teraz możesz emitować zdarzenia i nasłuchiwać emitowanych zdarzeń w ten sposób;
this.$eventBus.$on('say-hello', alertMe) this.$eventBus.$emit('pass-message', 'Event Bus says Hi')
Istnieje wiele baz kodu Vue wypełnionych takim kodem. Jednak w przypadku Vue 3 byłoby to niemożliwe, ponieważ $on
, $off
i $once
zostały usunięte, ale $emit
jest nadal dostępny, ponieważ komponent potomny musi emitować zdarzenia do swoich komponentów nadrzędnych. Alternatywą do tego byłoby użycie provide / inject
lub dowolnej z zalecanych bibliotek innych firm.
Wniosek
W tym artykule omówiliśmy, w jaki sposób można przekazywać dane z komponentu nadrzędnego do głęboko zagnieżdżonego komponentu podrzędnego za pomocą pary provide / inject
. Przyjrzeliśmy się również, jak możemy zmieniać położenie i przenosić komponenty z jednego punktu naszej aplikacji do drugiego. Inną rzeczą, którą przyjrzeliśmy się, jest komponent węzła z wieloma rootami i sposób, w jaki zapewniamy dystrybucję atrybutów, aby działały poprawnie. Na koniec omówiliśmy również zmiany w interfejsach Events API i Global API.
Dalsze zasoby
- „Fabryczne funkcje JavaScript w ES6+”, Eric Elliott, Medium
- „Korzystanie z magistrali zdarzeń do udostępniania rekwizytów między komponentami Vue”, Kingsley Silas, CSS-Tricks
- Korzystanie z wielu teleportów w tym samym celu, Vue.js Docs
- Atrybuty niezwiązane z propozycją, dokumenty Vue.js
- Praca z reaktywnością, Vue.js Docs
-
teleport
, Vue.js Docs - Fragmenty, dokumenty Vue.js
- Składnia 2.x, dokumenty Vue.js