Ce este nou în Vue 3?
Publicat: 2022-03-10Odată cu lansarea Vue 3, dezvoltatorii trebuie să facă upgrade de la Vue 2, deoarece vine cu o mână de funcții noi, care sunt foarte utile în construirea de componente ușor de citit și de întreținut și modalități îmbunătățite de a structura aplicația noastră în Vue. Vom arunca o privire asupra unora dintre aceste caracteristici în acest articol.
La sfârșitul acestui tutorial, cititorii vor;
- Aflați despre
provide / inject
și cum să le utilizați. - Aveți o înțelegere de bază despre Teleport și cum să îl utilizați.
- Aflați despre fragmente și cum să le folosiți.
- Aflați despre modificările aduse API-ului Global Vue.
- Aflați despre modificările aduse API-ului Events.
Acest articol se adresează celor care au o înțelegere adecvată a Vue 2.x. Puteți găsi tot codul folosit în acest exemplu în GitHub.
provide / inject
În Vue 2.x, am avut elemente de props
care au făcut mai ușoară transmiterea datelor (șir, matrice, obiecte etc.) de la o componentă părinte direct la componenta ei copil. Dar în timpul dezvoltării, am găsit adesea cazuri în care trebuia să transmitem date de la componenta părinte la o componentă profund imbricată, care era mai dificil de realizat cu elementele de props
. Acest lucru a dus la utilizarea Vuex Store, Event Hub și, uneori, la trecerea datelor prin componentele profund imbricate. Să ne uităm la o aplicație simplă;
Este important de reținut că Vue 2.2.0 a venit și cu provide / inject
, care nu a fost recomandată pentru utilizarea în codul de aplicație generic.
# 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>
Aici, avem o pagină de destinație cu un dropdown care conține o listă de culori și trecem color
selectată către childComponent.vue
ca prop. Această componentă copil are, de asemenea, un msg
de reclamă care acceptă un text de afișat în secțiunea șablon. În cele din urmă, această componentă are o componentă copil ( colorComponent.vue
) care acceptă o reclamă de color
de la componenta părinte care este utilizată la determinarea clasei pentru textul din această componentă. Acesta este un exemplu de trecere a datelor prin toate componentele.
Dar cu Vue 3, putem face acest lucru într-un mod mai curat și scurt, folosind noua pereche Furnizare și injectare. După cum sugerează și numele, folosim provide
fie ca funcție, fie ca obiect pentru a face datele disponibile de la o componentă părinte către oricare dintre componentele imbricate, indiferent de cât de adânc este imbricată o astfel de componentă. Folosim forma obiectului când transmitem valori hard-coded pentru a provide
astfel;
# 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>
Dar pentru cazurile în care trebuie să transmiteți o proprietate a instanței componente pentru a provide
, folosim modul funcție, astfel încât acest lucru este posibil;
# 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>
Deoarece nu avem nevoie de elementele de recuzită de color
atât în childComponent.vue
, cât și în colorComponent.vue
, scăpăm de el. Lucrul bun despre utilizarea provide
este că componenta părinte nu trebuie să știe care componentă are nevoie de proprietatea pe care o oferă.
Pentru a folosi acest lucru în componenta care are nevoie de el în acest caz, colorComponent.vue
facem acest lucru;
# 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>
Aici, folosim inject
care preia o serie de variabilele necesare de care are nevoie componenta. În acest caz, avem nevoie doar de proprietatea color
, așa că trecem doar de aceasta. După aceea, putem folosi color
în același mod în care o folosim atunci când folosim recuzită.
S-ar putea să observăm că, dacă încercăm să selectăm o culoare nouă folosind meniul derulant, culoarea nu se actualizează în colorComponent.vue
și asta pentru că în mod implicit proprietățile din provide
nu sunt reactive. Pentru a remedia asta, folosim metoda 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>
Aici, importăm computed
și trecem culoarea noastră selectedColor
, astfel încât să poată fi reactivă și actualizată pe măsură ce utilizatorul selectează o culoare diferită. Când treceți o variabilă metodei calculate, aceasta returnează un obiect care are o value
. Această proprietate deține valoarea variabilei dvs., așa că pentru acest exemplu, ar trebui să actualizăm colorComponent.vue
pentru a arăta astfel;
# 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>
Aici, schimbăm color
în color.value
pentru a reprezenta schimbarea după ce facem color
reactivă folosind metoda computed
. În acest moment, class
textului din această componentă se va schimba întotdeauna ori de câte ori selectedColor
se schimbă în componenta părinte.
Teleportați
Există cazuri în care creăm componente și le plasăm într-o parte a aplicației noastre din cauza logicii pe care o folosește aplicația, dar sunt destinate să fie afișate într-o altă parte a aplicației noastre. Un exemplu comun în acest sens ar fi un modal sau un pop-up care este menit să afișeze și să acopere întregul ecran. Deși putem crea o soluție pentru aceasta folosind proprietatea de position
a CSS pe astfel de elemente, cu Vue 3, putem folosi și folosind Teleport.
Teleportul ne permite să scoatem o componentă din poziția inițială într-un document, din containerul #app
implicit, aplicațiile Vue sunt împachetate și să o mutăm în orice element existent pe pagina în care este utilizat. Un exemplu bun ar fi folosirea Teleportului pentru a muta o componentă antet din interiorul #app
div într-un header
. Este important să rețineți că puteți Teleporta numai la elemente care există în afara Vue DOM.


Componenta Teleport acceptă două elemente de recuzită care determină comportamentul acestei componente și acestea sunt;
-
to
Acest prop acceptă fie un nume de clasă, un id, un element sau un atribut data-*. De asemenea, putem face această valoare dinamică pasând un:to
prop spre deosebireto
și schimbând elementul Teleport în mod dinamic. -
:disabled
Acest suport acceptă unBoolean
și poate fi folosit pentru a comuta caracteristica Teleport pe un element sau componentă. Acest lucru poate fi util pentru schimbarea dinamică a poziției unui element.
Un exemplu ideal de utilizare a Teleportului arată astfel;
# 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>
În fișierul implicit index.html
din aplicația ta Vue, adăugăm un element header
deoarece dorim să teleportăm componenta antet în acel punct în aplicația noastră. Am adăugat, de asemenea, o clasă acestui element pentru stil și pentru referire ușoară în componenta noastră 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>
Aici, creăm componenta antet și adăugăm un logo cu un link către pagina de pornire din aplicația noastră. Adăugăm, de asemenea, componenta Teleport și dăm lui to
prop o valoare a header
, deoarece dorim ca această componentă să fie redată în interiorul acestui element. În cele din urmă, importăm această componentă în aplicația noastră;
# App.vue <template> <router-view /> <app-header></app-header> </template> <script> import appHeader from "@/components/Header.vue"; export default { components: { appHeader, }, }; </script>
În acest fișier, importăm componenta antet și o plasăm în șablon, astfel încât să poată fi vizibilă în aplicația noastră.
Acum, dacă inspectăm elementul aplicației noastre, am observa că componenta antet este în interiorul elementului header
;

Fragmente
Cu Vue 2.x, a fost imposibil să aveți mai multe elemente rădăcină în template
fișierului și, ca o soluție, dezvoltatorii au început să încapsuleze toate elementele într-un element părinte. Deși aceasta nu pare a fi o problemă serioasă, există cazuri în care dezvoltatorii doresc să redeze o componentă fără un container care să înfășoare astfel de elemente, dar trebuie să se descurce cu asta.
Cu Vue 3, a fost introdusă o nouă caracteristică numită Fragmente și această caracteristică permite dezvoltatorilor să aibă mai multe elemente în fișierul șablon rădăcină. Deci, cu Vue 2.x, așa ar arăta o componentă de container de câmp de intrare;
# 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>
Aici, avem o componentă simplă a elementului de formular care acceptă două elemente de recuzită, label
și type
, iar secțiunea de șablon a acestei componente este înfășurată într-un div. Aceasta nu este neapărat o problemă, dar dacă doriți ca eticheta și câmpul de intrare să fie direct în elementul form
. Cu Vue 3, dezvoltatorii pot rescrie cu ușurință această componentă pentru a arăta astfel;
# inputComponent.vue <template class="testingss"> <label :for="label">{{ label }}</label> <input :type="type" : :name="label" /> </template>
Cu un singur nod rădăcină, atributele sunt întotdeauna atribuite nodului rădăcină și sunt cunoscute și ca atribute non-prop . Sunt evenimente sau atribute transmise unei componente care nu au proprietăți corespunzătoare definite în elemente de props
sau emits
. Exemple de astfel de atribute sunt class
și id
. Este, totuși, necesar să se definească în mod explicit căruia dintre elementele dintr-o componentă de nod multi-rădăcină ar trebui să fie atribuită.
Iată ce înseamnă acest lucru folosind inputComponent.vue
de sus;
- La adăugarea unei
class
la această componentă în componenta părinte, trebuie specificat căreia i-ar fi atribuită aceastăclass
, altfel atributul nu are efect.
<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>
Când faci așa ceva fără a defini unde ar trebui să fie atribuite atributele, primești acest avertisment în consola ta;

Iar border
nu are efect asupra componentei;

- Pentru a remedia acest lucru, adăugați un
v-bind="$attrs"
pe elementul căruia doriți să fie distribuite astfel de atribute;
<template> <label :for="label" v-bind="$attrs">{{ label }}</label> <input :type="type" : :name="label" /> </template>
Aici, îi spunem lui Vue că vrem ca atributele să fie distribuite elementului label
, ceea ce înseamnă că vrem ca awesome__class
să fie aplicat acestuia. Acum, dacă ne inspectăm elementul în browser, am vedea că clasa a fost acum adăugată la label
și, prin urmare, o chenar este acum în jurul etichetei.

API global
Nu era neobișnuit să vedeți Vue.component
sau Vue.use
în fișierul main.js
al unei aplicații Vue. Aceste tipuri de metode sunt cunoscute ca API-uri globale și există un număr destul de mare de ele în Vue 2.x. Una dintre provocările acestei metode este că face imposibilă izolarea anumitor funcționalități într-o singură instanță a aplicației dvs. (dacă aveți mai multe instanțe în aplicație) fără ca aceasta să afecteze alte aplicații, deoarece toate sunt montate pe Vue. La asta vreau să spun;
Vue.directive('focus', { inserted: el => el.focus() }) Vue.mixin({ /* ... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })
Pentru codul de mai sus, este imposibil să se precizeze că Directiva Vue să fie asociată cu app1
și Mixin cu app2
, dar în schimb, ambele sunt disponibile în cele două aplicații.
Vue 3 vine cu un nou API global în încercarea de a rezolva acest tip de problemă cu introducerea createApp
. Această metodă returnează o nouă instanță a unei aplicații Vue. O instanță de aplicație expune un subset al API-urilor globale actuale. Cu aceasta, toate API-urile (componentă, mixin, directivă, utilizare etc.) care modifică Vue
din Vue 2.x vor fi acum mutate în instanțe individuale de aplicație și acum, fiecare instanță a aplicației tale Vue poate avea funcționalități unice pentru fără a afecta alte aplicații existente.
Acum, codul de mai sus poate fi rescris ca;
const app1 = createApp({}) const app2 = createApp({}) app1.directive('focus', { inserted: el => el.focus() }) app2.mixin({ /* ... */ })
Cu toate acestea, este posibil să creați funcționalități pe care doriți să le partajați între toate aplicațiile dvs. și acest lucru se poate face folosind o funcție din fabrică.
Events API
Una dintre cele mai obișnuite moduri pe care le-au adoptat dezvoltatorii pentru a transmite date între componentele care nu au o relație părinte-copil, în afară de utilizarea Magazinului Vuex, este utilizarea Event Bus. Unul dintre motivele pentru care această metodă este comună este cât de ușor este să începi cu ea;
# eventBus.js const eventBus = new Vue() export default eventBus;
După aceasta, următorul lucru ar fi să importați acest fișier în main.js
pentru a-l face disponibil la nivel global în aplicația noastră sau pentru a-l importa în fișierele de care aveți nevoie;
# main.js import eventBus from 'eventBus' Vue.prototype.$eventBus = eventBus
Acum, puteți emite evenimente și puteți asculta evenimente emise ca acesta;
this.$eventBus.$on('say-hello', alertMe) this.$eventBus.$emit('pass-message', 'Event Bus says Hi')
Există o mulțime de bază de cod Vue care este umplută cu cod ca acesta. Cu toate acestea, cu Vue 3, ar fi imposibil de făcut, deoarece $on
, $off
și $once
au fost toate eliminate, dar $emit
este încă disponibil deoarece este necesar ca componenta copii să emită evenimente către componentele lor părinte. O alternativă la aceasta ar fi utilizarea provide / inject
sau oricare dintre bibliotecile de la terți recomandate.
Concluzie
În acest articol, am descris cum puteți transfera date de la o componentă părinte la o componentă copil profund imbricată folosind perechea provide / inject
. Ne-am uitat, de asemenea, la modul în care putem repoziționa și transfera componente dintr-un punct al aplicației noastre în altul. Un alt lucru la care ne-am uitat este componenta nod multi-rădăcină și cum să ne asigurăm că distribuim atributele astfel încât acestea să funcționeze corect. În cele din urmă, am acoperit și modificările aduse API-ului Events și API-ului global.
Resurse suplimentare
- „Funcții JavaScript Factory cu ES6+”, Eric Elliott, Medium
- „Folosirea autobuzului de evenimente pentru a partaja elemente de recuzită între componentele Vue”, Kingsley Silas, CSS-Tricks
- Utilizarea mai multor teleportări pe aceeași țintă, Vue.js Docs
- Atribute non-prop, documente Vue.js
- Lucrul cu Reactivitate, Vue.js Docs
-
teleport
, Vue.js Docs - Fragmente, documente Vue.js
- Sintaxă 2.x, documente Vue.js