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>
- Spring Boot mateix; Vaig decidir l'enfocament regular i no reactiu
- Integració de Spring Boot Thymeleaf
- Localitzador de WebJars, per evitar especificar la versió de Vue al costat del client
- 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 } }
- Passeu una llista estàtica d'objectes
Todo
- Vegeu a continuació
- 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:
- 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
- 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/axios@1.7/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>
- Axios ajuda a fer sol·licituds HTTP
- Vue en si
- El nostre codi del costat del client
- 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:
- Després de carregar la pàgina, la pàgina hauria de mostrar tots els elements
Todo
- Quan feu clic a una casella de selecció
Todo
completat, hauria d'establir/desactivar l'atributcompleted
- Quan feu clic al botó Neteja , s'elimina tot
Todo
que heu completat - En fer clic al botó Afegeix , hauria d'afegir una
Todo
a la llista deTodo
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 perlabel
-
completed
: s'estableix comfalse
-
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 }
- 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>
- Establiu l'identificador per enquadernar fàcilment
- 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, }
- 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');
- Configura el component
- Vue espera la funció
render()
-
h()
per a l'hiperscript crea un node virtual a partir de l'objecte i les seves propietats - 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>
- Mostra l'identificador
Todo
- Mostra l'etiqueta
Todo
- Marqueu la casella si el seu atribut
completed
éstrue
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 } }
- Accepteu la matriu
props
, perquè més tard hi puguem accedir - Vue passa l'
event
que va activar la trucada - Axios és una llibreria de JavaScript que simplifica les trucades HTTP
- 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.
- Càrrega útil JSON
- 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 } }
- Com més amunt
- Axios ofereix una conversió JSON automatitzada de la trucada HTTP
-
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.
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');
- Obteniu el conjunt de dades a la pàgina HTML, mitjançant Thymeleaf, tal com s'ha explicat anteriorment
- 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. - Torna els referents, segons les expectatives de Vue
- Mira, mare, estic fent servir l'operador de propagació de JavaScript
- 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>
- Recorreu la llista d'objectes
Todo
- 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 } } }
- Creeu un embolcall reactiu al voltant del títol l'abast del qual es limita a la funció
- La funció
create()
pròpiament dita - Afegiu el nou objecte JSON retornat per la crida de l'API a la llista de
Todo
- Restableix el valor del camp
- 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à: