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 / injecti 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:toprop w przeciwieństwietodynamicznego zmieniania elementu Teleport. -
:disabled
Ten rekwizyt przyjmuje wartościBooleani 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
classdo 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 = eventBusTeraz 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
