paint-brush
Forøgelse af klienten med Vue.jsved@nfrankel
Ny historie

Forøgelse af klienten med Vue.js

ved Nicolas Fränkel17m2024/09/26
Read on Terminal Reader

For langt; At læse

I dette indlæg tog jeg mine første skridt i at udvide en SSR-app med Vue. Det var ret ligetil. Det største problem, jeg stødte på, var, at Vue skulle erstatte linjeskabelonen: Jeg læste ikke dokumentationen grundigt og savnede is-attributten.
featured image - Forøgelse af klienten med Vue.js
Nicolas Fränkel HackerNoon profile picture
0-item


I mit tidligere indlæg lagde jeg grunden til at bygge videre på; nu er det tid til at starte "for alvor".


Jeg har hørt meget om Vue.js. Derudover fortalte en ven, der gik fra udvikler til manager, mig gode ting om Vue, hvilket yderligere vækkede min interesse. Jeg besluttede at tage et kig på det: det vil være den første "lette" JavaScript-ramme, jeg vil studere - fra en nybegynders synspunkt, som jeg er.

Udlægning af arbejdet

Jeg forklarede WebJars og Thymeleaf i det sidste indlæg. Her er opsætningen, server- og klientsiden.

Server-side

Sådan integrerer jeg begge i POM:


 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!--1--> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <!--2--> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> <!--3--> <version>0.52</version> </dependency> <dependency> <groupId>org.webjars.npm</groupId> <artifactId>vue</artifactId> <!--4--> <version>3.4.34</version> </dependency> </dependencies>
  1. Spring Boot selv; Jeg besluttede mig for den almindelige, ikke-reaktive tilgang
  2. Spring Boot Thymeleaf integration
  3. WebJars locator, for at undgå at angive Vue-versionen på klientsiden
  4. Vue, endelig!


Jeg bruger Kotlin Router og Bean DSL'er på Spring Boot-siden:


 fun vue(todos: List<Todo>) = router { //1 GET("/vue") { ok().render("vue", mapOf("title" to "Vue.js", "todos" to todos)) //2-3 } }
  1. Send en statisk liste over Todo objekter
  2. Se nedenfor
  3. Send modellen til Thymeleaf


Hvis du er vant til at udvikle API'er, er du bekendt med body() -funktionen; det returnerer nyttelasten direkte, sandsynligvis i JSON-format. render() sender flowet til visningsteknologien, i dette tilfælde Thymeleaf. Den accepterer to parametre:


  1. Udsigtens navn. Som standard er stien /templates og præfikset er .html ; i dette tilfælde forventer Thymeleaf en visning på /templates/vue.html
  2. Et modelkort over nøgleværdi-par

Kundesiden

Her er koden på HTML-siden:


 <script th:src="@{/webjars/axios/dist/axios.js}" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script> <!--1--> <script th:src="@{/webjars/vue/dist/vue.global.js}" src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script> <!--2--> <script th:src="@{/vue.js}" src="../static/vue.js"></script> <!--3--> <script th:inline="javascript"> /*<![CDATA[*/ window.vueData = { <!--4--> title: /*[[${ title }]]*/ 'A Title', todos: /*[[${ todos }]]*/ [{ 'id': 1, 'label': 'Take out the trash', 'completed': false }] }; /*]]>*/ </script>
  1. Axios hjælper med at lave HTTP-anmodninger
  2. Vue selv
  3. Vores kode på klientsiden
  4. Indstil dataene


Som forklaret i sidste uges artikel, er en af Thymeleafs fordele, at den tillader både statisk filgengivelse og gengivelse på serversiden. For at få magien til at virke, specificerer jeg en sti på klientsiden, dvs. , src , og en sti på serversiden, dvs. th:src .


Vue-koden

Lad os nu dykke ned i Vue-koden.

Vi ønsker at implementere flere funktioner:


  1. Efter sideindlæsningen skal siden vise alle Todo elementer
  2. Når du klikker på afkrydsningsfeltet Todo gennemført, skal det aktivere/deaktivere den completed attribut
  3. Når du klikker på knappen Oprydning , sletter den alle fuldførte Todo
  4. Når du klikker på knappen Tilføj , skal den tilføje en Todo til listen over Todo med følgende værdier:
    • id : Server-side beregnet ID som det maksimale af alle andre ID'er plus 1
    • label : værdien af feltet Label for label
    • completed : indstillet til false

Vores første skridt ind i Vue

Det første skridt er at bootstrap rammen. Vi har allerede oprettet referencen til vores tilpassede vue.js -fil ovenfor.


 document.addEventListener('DOMContentLoaded', () => { //1 // The next JavaScript code snippets will be inside the block }
  1. Kør blokken, når DOM er færdig med at indlæse


Det næste trin er at lade Vue administrere en del af siden. På HTML-siden skal vi beslutte, hvilken del på øverste niveau Vue administrerer. Vi kan vælge en vilkårlig <div> og ændre den senere, hvis det er nødvendigt.


 <div id="app"> </div>


På JavaScript-siden opretter vi en app , der passerer CSS-vælgeren for den tidligere HTML <div> .


 Vue.createApp({}).mount('#app');


På dette tidspunkt starter vi Vue, når siden indlæses, men der sker ikke noget synligt.


Det næste trin er at oprette en Vue -skabelon . En Vue-skabelon er en almindelig HTML <template> der administreres af Vue. Du kan definere Vue i Javascript, men jeg foretrækker at gøre det på HTML-siden.


Lad os starte med en rodskabelon, der kan vise titlen.


 <template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
  1. Indstil ID for nem binding
  2. Brug title egenskaben; det mangler at blive sat op


På JavaScript-siden skal vi oprette administrationskoden.


 const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }
  1. Erklær title egenskaben, den der bruges i HTML-skabelonen


Endelig skal vi videregive dette objekt, når vi opretter appen:


 Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
  1. Konfigurer komponenten
  2. Vue forventer render() -funktionen
  3. h() for hyperscript opretter en virtuel node ud af objektet og dets egenskaber
  4. Initialiser title med den værdigenererede serverside


På dette tidspunkt viser Vue titlen.

Grundlæggende interaktioner

På dette tidspunkt kan vi implementere handlingen, når brugeren klikker på et afkrydsningsfelt: det skal opdateres på serversiden.

Først tilføjede jeg en ny indlejret Vue-skabelon til tabellen, der viser Todo . For at undgå at forlænge indlægget, vil jeg undgå at beskrive det i detaljer. Hvis du er interesseret, så tag et kig på kildekoden .


Her er startlinjeskabelonens kode, henholdsvis JavaScript og HTML:


 const TodoLine = { props: ['todo'], template: document.getElementById('todo-line').innerHTML }
 <template id="todo-line"> <tr> <td>{{ todo.id }}</td> <!--1--> <td>{{ todo.label }}</td> <!--2--> <td> <label> <input type="checkbox" :checked="todo.completed" /> </label> </td> </tr> </template>
  1. Vis Todo -id'et
  2. Vis Todo -etiketten
  3. Marker afkrydsningsfeltet, hvis dens completed attribut er true


Vue tillader hændelseshåndtering via @ -syntaksen.


 <input type="checkbox" :checked="todo.completed" @click="check" />


Vue kalder skabelonens check() funktion, når brugeren klikker på linjen. Vi definerer denne funktion i en setup() parameter:


 const TodoLine = { props: ['todo'], template: document.getElementById('todo-line').innerHTML, setup(props) { //1 const check = function (event) { //2 const { todo } = props axios.patch( //3 `/api/todo/${todo.id}`, //4 { checked: event.target.checked } //5 ) } return { check } //6 } }
  1. Accepter props -arrayet, så vi senere kan få adgang til det
  2. Vue sender den event , der udløste opkaldet
  3. Axios er et JavaScript-lib, der forenkler HTTP-kald
  4. Serversiden skal levere en API; det er uden for dette indlægs rammer, men du er velkommen til at tjekke kildekoden.
  5. JSON nyttelast
  6. Vi returnerer alle definerede funktioner for at gøre dem tilgængelige fra HTML

Model på klientsiden

I det forrige afsnit lavede jeg to fejl:


  • Jeg styrede ikke nogen lokal model
  • Jeg brugte ikke HTTP-svarets opkaldsmetode


Det vil vi gøre ved at implementere den næste funktion, som er oprydning af afsluttede opgaver.


Vi ved nu, hvordan vi håndterer begivenheder via Vue:


 <button class="btn btn-warning" @click="cleanup">Cleanup</button>


TodosApp objektet tilføjer vi en funktion af samme navn:


 const TodosApp = { props: ['title', 'todos'], components: { TodoLine }, template: document.getElementById('todos-app').innerHTML, setup() { const cleanup = function() { //1 axios.delete('/api/todo:cleanup').then(response => { //1 state.value.todos = response.data //2-3 }) } return { cleanup } //1 } }
  1. Som ovenfor
  2. Axios tilbyder automatisk JSON-konvertering af HTTP-kaldet
  3. state er hvor vi opbevarer modellen


I Vues semantik er Vue-modellen en indpakning omkring data, som vi ønsker skal være reaktive . Reaktiv betyder tovejsbinding mellem udsigten og modellen. Vi kan gøre en eksisterende værdi reaktiv ved at overføre den til ref() metoden:


I Composition API er den anbefalede måde at erklære reaktiv tilstand på at bruge ref() -funktionen.


ref() tager argumentet og returnerer det pakket ind i et ref-objekt med en .value-egenskab.


For at få adgang til refs i en komponents skabelon skal du deklarere og returnere dem fra en komponents setup() -funktion.


-- Erklærer reaktiv tilstand


Lad os gøre det:


 const state = ref({ title: window.vueData.title, //1-2 todos: window.vueData.todos, //1 }) createApp({ components: { TodosApp }, setup() { return { ...state.value } //3-4 }, render() { return h(TodosApp, { todos: state.value.todos, //5 title: state.value.title, //5 }) } }).mount('#app');
  1. Hent datasættet på HTML-siden via Thymeleaf, som forklaret ovenfor
  2. Vi ændrer måden, vi angiver title på. Det er ikke nødvendigt, da der ikke er nogen tovejsbinding - vi opdaterer ikke titlen på klientsiden, men jeg foretrækker at holde håndteringen sammenhængende på tværs af alle værdier
  3. Returner dommerne i henhold til Vues forventninger
  4. Se, mor, jeg bruger JavaScript-spredningsoperatoren
  5. Konfigurer objektets attributter fra state


På dette tidspunkt har vi en reaktiv model på klientsiden.


På HTML-siden bruger vi de relevante Vue-attributter:


 <tbody> <tr is="vue:todo-line" v-for="todo in todos" :key="todo.id" :todo="todo"></tr> <!--1-2--> </tbody>
  1. Sløjfe over listen over Todo objekter
  2. is -attributten er afgørende for at klare den måde, browseren analyserer HTML på. Se Vue-dokumentationen for flere detaljer

Jeg har beskrevet den tilsvarende skabelon ovenfor.

Opdatering af modellen

Vi kan nu implementere en ny funktion: tilføje en ny Todo fra klienten. Når vi klikker på knappen Tilføj , læser vi Label- feltets værdi, sender dataene til API'et og opdaterer modellen med svaret.


Her er den opdaterede kode:


 const TodosApp = { props: ['title', 'todos'], components: { TodoLine }, template: document.getElementById('todos-app').innerHTML, setup() { const label = ref('') //1 const create = function() { //2 axios.post('/api/todo', { label: label.value }).then(response => { state.value.todos.push(response.data) //3 }).then(() => { label.value = '' //4 }) } const cleanup = function() { axios.delete('/api/todo:cleanup').then(response => { state.value.todos = response.data //5 }) } return { label, create, cleanup } } }
  1. Opret en reaktiv indpakning omkring titlen, hvis omfang er begrænset til funktionen
  2. Den egentlige create() funktion
  3. Føj det nye JSON-objekt, der returneres af API-kaldet, til listen over Todo
  4. Nulstil feltets værdi
  5. Erstat hele listen ved sletning; mekanismen er den samme


På HTML-siden tilføjer vi en knap og binder til create() -funktionen. Ligeledes tilføjer vi feltet Label og binder det til modellen.


 <form> <div class="form-group row"> <label for="new-todo-label" class="col-auto col-form-label">New task</label> <div class="col-10"> <input type="text" id="new-todo-label" placeholder="Label" class="form-control" v-model="label" /> </div> <div class="col-auto"> <button type="button" class="btn btn-success" @click="create">Add</button> </div> </div> </form>


Vue binder funktionen create() til HTML-knappen. Det kalder det asynkront og opdaterer den reaktive Todo liste med det nye element, der returneres af opkaldet. Vi gør det samme for knappen Oprydning for at fjerne afkrydsede Todo objekter.


Bemærk, at jeg ikke med vilje implementerede nogen fejlhåndteringskode for at undgå at gøre koden mere kompleks end nødvendigt. Jeg stopper her, da vi har fået nok indsigt til en første oplevelse.

Konklusion

I dette indlæg tog jeg mine første skridt i at udvide en SSR-app med Vue. Det var ret ligetil. Det største problem, jeg stødte på, var, at Vue skulle erstatte linjeskabelonen: Jeg læste ikke dokumentationen grundigt og savnede is -attributten.


Jeg var dog nødt til at skrive en del linjer JavaScript, selvom jeg brugte Axios til at hjælpe mig med HTTP-kald og ikke klarede fejl.


I det næste indlæg vil jeg implementere de samme funktioner med Alpine.js.


Den komplette kildekode til dette indlæg kan findes på GitHub:

Gå videre: