Ce este nou în Vue 3?

Publicat: 2022-03-10
Rezumat rapid ↬ Vue 3 vine cu o mulțime de caracteristici noi interesante și modificări la unele dintre cele existente, care au ca scop să facă dezvoltarea cu framework-ul mult mai ușoară și ușor de întreținut. În acest articol, vom arunca o privire asupra unora dintre aceste noi funcții și cum să începem cu ele. De asemenea, vom arunca o privire asupra unora dintre modificările aduse funcțiilor existente.

Odată 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;

  1. Aflați despre provide / inject și cum să le utilizați.
  2. Aveți o înțelegere de bază despre Teleport și cum să îl utilizați.
  3. Aflați despre fragmente și cum să le folosiți.
  4. Aflați despre modificările aduse API-ului Global Vue.
  5. 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.

Mai multe după săritură! Continuați să citiți mai jos ↓

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.

mesaj de eroare în consolă când vă teleportați la un element nevalid
Mesaj de eroare de teleportare în consolă: mesaj de eroare de țintă de teleportare nevalid în terminal. (Previzualizare mare)

Componenta Teleport acceptă două elemente de recuzită care determină comportamentul acestei componente și acestea sunt;

  1. 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 deosebire to și schimbând elementul Teleport în mod dinamic.
  2. :disabled
    Acest suport acceptă un Boolean ș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 ;

Componenta antet în DevTools
Componenta antet în DevTools (previzualizare mare)

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;

  1. 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;

mesaj de eroare în terminal când atributele nu sunt distribuite
Mesaj de eroare în terminal când atributele nu sunt distribuite (previzualizare mare)

Iar border nu are efect asupra componentei;

componentă fără distribuție de atribute
Componentă fără distribuție de atribute (previzualizare mare)
  1. 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.

componentă cu distribuție de atribute
Componentă cu distribuție de atribute (previzualizare mare)

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