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.
Vaig explicar WebJars i Thymeleaf a l'última publicació. Aquí teniu la configuració, servidor i client.
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>
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 } }
Todo
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:
/templates
i el prefix és .html
; en aquest cas, Thymeleaf espera una vista a /templates/vue.html
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>
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
.
Ara, endinsem-nos en el codi Vue.
Volem implementar diverses característiques:
Todo
Todo
completat, hauria d'establir/desactivar l'atribut completed
Todo
que heu completatTodo
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 1label
: valor del camp Etiqueta per label
completed
: s'estableix com false
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 }
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>
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, }
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');
render()
h()
per a l'hiperscript crea un node virtual a partir de l'objecte i les seves propietatstitle
amb el valor generat al costat del servidor
En aquest punt, Vue mostra el títol.
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>
Todo
Todo
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 } }
props
, perquè més tard hi puguem accedirevent
que va activar la trucadaA l'apartat anterior vaig cometre dos errors:
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 } }
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');
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.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>
Todo
is
és crucial per fer front a la manera com el navegador analitza HTML. Consulteu la documentació de Vue per obtenir més detallsHe descrit la plantilla corresponent més amunt.
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 } } }
create()
pròpiament ditaTodo
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.
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à: