Co nowego w Vue 3?

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ Vue 3 zawiera wiele interesujących nowych funkcji i zmian w niektórych z istniejących, które mają na celu ułatwienie i utrzymanie programowania za pomocą frameworka. W tym artykule przyjrzymy się niektórym z tych nowych funkcji i sposobom korzystania z nich. Przyjrzymy się również niektórym zmianom wprowadzonym w istniejących funkcjach.

Wraz 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ą;

  1. Dowiedz się, jak provide / inject i jak z niego korzystać.
  2. Masz podstawową wiedzę na temat Teleportu i tego, jak z niego korzystać.
  3. Dowiedz się o fragmentach i o tym, jak z nich korzystać.
  4. Dowiedz się o zmianach wprowadzonych w Global Vue API.
  5. 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 .

Więcej po skoku! Kontynuuj czytanie poniżej ↓

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.

komunikat o błędzie w konsoli, gdy teleportujesz się do nieprawidłowego elementu
Komunikat o błędzie teleportacji w konsoli: Komunikat o błędzie nieprawidłowego celu teleportacji w terminalu. (duży podgląd)

Komponent Teleport akceptuje dwie rekwizyty, które określają zachowanie tego komponentu i są one;

  1. 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ństwie to dynamicznego zmieniania elementu Teleport.
  2. :disabled
    Ten rekwizyt przyjmuje wartości Boolean 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 ;

Komponent nagłówka w DevTools
Komponent nagłówka w DevTools (duży podgląd)

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;

  1. Dodając class do tego komponentu w komponencie nadrzędnym, należy określić, do którego komponentu zostanie przypisana ta class , 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;

komunikat o błędzie w terminalu, gdy atrybuty nie są dystrybuowane
Komunikat o błędzie w terminalu, gdy atrybuty nie są dystrybuowane (duży podgląd)

A border nie ma wpływu na komponent;

komponent bez dystrybucji atrybutów
Komponent bez dystrybucji atrybutów (duży podgląd)
  1. 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.

komponent z rozkładem atrybutów
Komponent z rozkładem atrybutów (duży podgląd)

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