A la meva publicació anterior , vaig posar el terreny per construir-hi; ara és el moment de començar "de veritat".
<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>
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
<script th:src="@{/webjars/axios/dist/axios.js}" src="//cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script> <!--1--> <script th:src="@{/webjars/vue/dist/vue.global.js}" src="//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
.
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');
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.
<template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
title
; queda per configurar
const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }
title
, la que s'utilitza a la plantilla HTML
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
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 .
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 trucada
<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.--
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.
<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 per obtenir més detalls 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.
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.
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
.
Anar més enllà: