paint-brush
Augment del client amb Vue.jsper@nfrankel
Nova Història

Augment del client amb Vue.js

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

Massa Llarg; Per llegir

En aquesta publicació, vaig fer els meus primers passos per augmentar una aplicació SSR amb Vue. Va ser bastant senzill. El problema més gran que em vaig trobar va ser que Vue substituís la plantilla de línia: no vaig llegir gaire la documentació i vaig perdre l'atribut is.
featured image - Augment del client amb Vue.js
Nicolas Fränkel HackerNoon profile picture
0-item


A la meva publicació anterior , vaig posar el terreny per construir-hi; ara és el moment de començar "de veritat".


Vaig escoltar molt de Vue.js. A més, un amic que va passar de desenvolupador a gestor em va dir coses bones sobre Vue, cosa que va despertar encara més el meu interès. Vaig decidir fer-hi una ullada: serà el primer framework de JavaScript "lleuger" que estudiaré, des del punt de vista d'un principiant, que sóc.

Distribució de l'obra

Vaig explicar WebJars i Thymeleaf a l'última publicació. Aquí teniu la configuració, servidor i client.

Del costat del servidor

Així és com integro tots dos al 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 mateix; Vaig decidir l'enfocament regular i no reactiu
  2. Integració de Spring Boot Thymeleaf
  3. Localitzador de WebJars, per evitar especificar la versió de Vue al costat del client
  4. Mira, per fi!


Estic utilitzant els DSL Kotlin Router i Bean al costat de Spring Boot:


 fun vue(todos: List<Todo>) = router { //1 GET("/vue") { ok().render("vue", mapOf("title" to "Vue.js", "todos" to todos)) //2-3 } }
  1. Passeu una llista estàtica d'objectes Todo
  2. Vegeu a continuació
  3. Passeu el model a Thymeleaf


Si estàs acostumat a desenvolupar API, estàs familiaritzat amb la funció body() ; retorna directament la càrrega útil, probablement en format JSON. El render() passa el flux a la tecnologia de visualització, en aquest cas, Thymeleaf. Admet dos paràmetres:


  1. Nom de la vista. Per defecte, el camí és /templates i el prefix és .html ; en aquest cas, Thymeleaf espera una vista a /templates/vue.html
  2. Un model de mapa de parells clau-valor

Del costat del client

Aquí teniu el codi a la part HTML:


 <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 ajuda a fer sol·licituds HTTP
  2. Vue en si
  3. El nostre codi del costat del client
  4. Estableix les dades


Tal com es va explicar a l'article de la setmana passada, un dels avantatges de Thymeleaf és que permet la representació de fitxers estàtics i la representació del costat del servidor. Perquè la màgia funcioni, especifico una ruta del costat del client, és a dir , src , i una ruta del costat del servidor, és a dir , th:src .


El codi Vue

Ara, endinsem-nos en el codi Vue.

Volem implementar diverses característiques:


  1. Després de carregar la pàgina, la pàgina hauria de mostrar tots els elements Todo
  2. Quan feu clic a una casella de selecció Todo completat, hauria d'establir/desactivar l'atribut completed
  3. Quan feu clic al botó Neteja , s'elimina tot Todo que heu completat
  4. En fer clic al botó Afegeix , hauria d'afegir una Todo a la llista de Todo amb els valors següents:
    • id : identificador calculat del servidor com a màxim de tots els altres identificadors més 1
    • label : valor del camp Etiqueta per label
    • completed : s'estableix com false

Els nostres primers passos a Vue

El primer pas és arrencar el marc. Ja hem configurat la referència del nostre fitxer vue.js personalitzat més amunt.


 document.addEventListener('DOMContentLoaded', () => { //1 // The next JavaScript code snippets will be inside the block }
  1. Executeu el bloc quan el DOM s'hagi acabat de carregar


El següent pas és deixar que Vue gestione part de la pàgina. Pel que fa a l'HTML, hem de decidir quina part de nivell superior gestiona Vue. Podem triar un <div> arbitrari i canviar-lo més tard si cal.


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


Al costat de JavaScript, creem una aplicació , passant el selector CSS de l'HTML anterior <div> .


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


En aquest punt, iniciem Vue quan es carregui la pàgina, però no passa res visible.


El següent pas és crear una plantilla Vue. Una plantilla Vue és una <template> HTML normal gestionada per Vue. Podeu definir Vue en Javascript, però prefereixo fer-ho a la pàgina HTML.


Comencem amb una plantilla arrel que pugui mostrar el títol.


 <template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
  1. Establiu l'identificador per enquadernar fàcilment
  2. Utilitzeu la propietat title ; queda per configurar


Pel costat de JavaScript, hem de crear el codi de gestió.


 const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }
  1. Declara la propietat title , la que s'utilitza a la plantilla HTML


Finalment, hem de passar aquest objecte quan creem l'aplicació:


 Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
  1. Configura el component
  2. Vue espera la funció render()
  3. h() per a l'hiperscript crea un node virtual a partir de l'objecte i les seves propietats
  4. Inicieu la propietat title amb el valor generat al costat del servidor


En aquest punt, Vue mostra el títol.

Interaccions bàsiques

En aquest punt, podem implementar l'acció quan l'usuari fa clic a una casella de selecció: cal actualitzar-la a l'estat del servidor.

Primer, he afegit una nova plantilla Vue imbricada per a la taula que mostra Todo . Per no allargar el post, evitaré descriure-lo amb detall. Si esteu interessats, feu una ullada al codi font .


Aquí teniu el codi de la plantilla de línia inicial, respectivament JavaScript i 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. Mostra l'identificador Todo
  2. Mostra l'etiqueta Todo
  3. Marqueu la casella si el seu atribut completed és true


Vue permet la gestió d'esdeveniments mitjançant la sintaxi @ .


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


Vue crida a la funció check() de la plantilla quan l'usuari fa clic a la línia. Definim aquesta funció en un paràmetre setup() :


 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. Accepteu la matriu props , perquè més tard hi puguem accedir
  2. Vue passa l' event que va activar la trucada
  3. Axios és una llibreria de JavaScript que simplifica les trucades HTTP
  4. El costat del servidor ha de proporcionar una API; està fora de l'abast d'aquesta publicació, però no dubteu a comprovar el codi font.
  5. Càrrega útil JSON
  6. Tornem totes les funcions definides per fer-les accessibles des d'HTML

Model del costat del client

A l'apartat anterior vaig cometre dos errors:


  • No vaig gestionar cap model local
  • No vaig utilitzar el mètode de trucada de la resposta HTTP


Ho farem implementant la següent característica, que és la neteja de les tasques completades.


Ara sabem com gestionar els esdeveniments mitjançant Vue:


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


A l'objecte TodosApp , afegim una funció del mateix nom:


 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. Com més amunt
  2. Axios ofereix una conversió JSON automatitzada de la trucada HTTP
  3. state és on emmagatzemem el model


En la semàntica de Vue, el model Vue és un embolcall al voltant de dades que volem que siguin reactius . Reactiu significa unió bidireccional entre la vista i el model. Podem fer reactiu un valor existent passant-lo al mètode ref() :


A l'API de composició, la manera recomanada de declarar l'estat reactiu és utilitzar la funció ref() .


ref() pren l'argument i el retorna embolicat dins d'un objecte ref amb una propietat .value.


Per accedir a les referències a la plantilla d'un component, declareu-les i retorneu-les des de la funció setup() d'un component.


-- Declaració d'estat reactiu


Fem-ho:


 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. Obteniu el conjunt de dades a la pàgina HTML, mitjançant Thymeleaf, tal com s'ha explicat anteriorment
  2. Canviem la manera com posem el title . No és necessari ja que no hi ha cap vinculació bidireccional: no actualitzem el títol del costat del client, però prefereixo mantenir la gestió coherent en tots els valors.
  3. Torna els referents, segons les expectatives de Vue
  4. Mira, mare, estic fent servir l'operador de propagació de JavaScript
  5. Configureu els atributs de l'objecte des de l' state


En aquest punt, tenim un model reactiu del costat del client.


Al costat HTML, utilitzem els atributs de Vue rellevants:


 <tbody> <tr is="vue:todo-line" v-for="todo in todos" :key="todo.id" :todo="todo"></tr> <!--1-2--> </tbody>
  1. Recorreu la llista d'objectes Todo
  2. L'atribut is és crucial per fer front a la manera com el navegador analitza HTML. Consulteu la documentació de Vue per obtenir més detalls

He descrit la plantilla corresponent més amunt.

Actualització del model

Ara podem implementar una nova característica: afegir una nova Todo des del client. En fer clic al botó Afegeix , llegim el valor del camp Etiqueta , enviem les dades a l'API i actualitzem el model amb la resposta.


Aquí teniu el codi actualitzat:


 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. Creeu un embolcall reactiu al voltant del títol l'abast del qual es limita a la funció
  2. La funció create() pròpiament dita
  3. Afegiu el nou objecte JSON retornat per la crida de l'API a la llista de Todo
  4. Restableix el valor del camp
  5. Substituïu tota la llista en suprimir; el mecanisme és el mateix


Al costat HTML, afegim un botó i enllacem a la funció create() . De la mateixa manera, afegim el camp Etiqueta i l'enllaçem al model.


 <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 enllaça la funció create() al botó HTML. Ho truca de manera asíncrona i actualitza la llista de Todo reactiva amb el nou element que retorna la trucada. Fem el mateix amb el botó Neteja , per eliminar els objectes Todo marcats.


Tingueu en compte que no he implementat intencionadament cap codi de gestió d'errors per evitar que el codi sigui més complex del necessari. M'aturaré aquí, ja que hem obtingut prou coneixements per a una primera experiència.

Conclusió

En aquesta publicació, vaig fer els meus primers passos per augmentar una aplicació SSR amb Vue. Va ser bastant senzill. El problema més gran que em vaig trobar va ser que Vue substituís la plantilla de línia: no vaig llegir gaire la documentació i vaig perdre l'atribut is .


Tanmateix, vaig haver d'escriure unes quantes línies de JavaScript, tot i que vaig utilitzar Axios per ajudar-me amb les trucades HTTP i no vaig gestionar els errors.


A la propera publicació, implementaré les mateixes funcions amb Alpine.js.


El codi font complet d'aquesta publicació es pot trobar a GitHub:

Anar més enllà: