Во мојот претходен пост , поставив основа за да се изградам; сега е време да се започне „вистински“.
<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
објекти
Ако сте навикнати да развивате API, запознаени сте со функцијата body()
; директно го враќа товарот, веројатно во JSON формат. render()
го пренесува протокот на технологијата за преглед, во овој случај, Thymeleaf. Прифаќа два параметри:
/templates
, а префиксот е .html
; во овој случај, Thymeleaf очекува преглед на /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>
Како што беше објаснето во минатонеделната статија, една од придобивките на Thymeleaf е тоа што овозможува и статичко прикажување на датотеки и прикажување од страна на серверот. За да може магијата да функционира, јас одредувам патека од клиентот, т.е. src
и патека од страна на серверот, т.е. th:src
.
Todo
Todo
завршено, треба да го постави/непостави completed
атрибутTodo
Todo
на листата на Todo
со следните вредности:id
: пресметан ID од страна на серверот како максимум од сите други ID плус 1label
: вредност на полето Label за label
completed
: поставено на false
Првиот чекор е да се подигне рамката. Веќе ја поставивме референцата за нашата приспособена датотека vue.js
погоре.
document.addEventListener('DOMContentLoaded', () => { //1 // The next JavaScript code snippets will be inside the block }
Следниот чекор е да му дозволите на Vue да управува со дел од страницата. На страната на HTML, ние мора да одлучиме со кој дел од највисоко ниво управува Vue. Можеме да избереме произволно <div>
и да го промениме подоцна ако е потребно.
<div id="app"> </div>
На страната JavaScript, создаваме апликација , поминувајќи го CSS избирачот на претходниот HTML <div>
.
Vue.createApp({}).mount('#app');
Следниот чекор е да креирате шаблон Vue. Шаблон Vue е обичен HTML <template>
управуван од Vue. Можете да го дефинирате Vue во Javascript, но јас претпочитам да го направам тоа на страницата HTML.
<template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
title
; останува да се постави
const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }
title
, она што се користи во шаблонот HTML
Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
render()
h()
за хиперскрипт создава виртуелен јазол од објектот и неговите својстваtitle
со генерирана вредност од страна на серверот
Прво, додадов нов вгнезден Vue шаблон за табелата што го прикажува Todo
. За да не го издолжам постот, ќе избегнам детално да го опишам. Ако сте заинтересирани, погледнете го .
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
idTodo
completed
атрибут е true
Vue овозможува управување со настани преку синтаксата @
.
<input type="checkbox" :checked="todo.completed" @click="check" />
Vue ја повикува функцијата check()
на шаблонот кога корисникот ќе кликне на линијата. Ја дефинираме оваа функција во параметарот 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
, за да можеме подоцна да пристапиме до неаevent
што го активирал повикот
<button class="btn btn-warning" @click="cleanup">Cleanup</button>
На објектот TodosApp
, додаваме функција со исто име:
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
е местото каде што го складираме моделот
Во семантиката на Vue, моделот Vue е обвивка околу податоците што сакаме да бидат реактивни . Реактивно значи двонасочно поврзување помеѓу погледот и моделот. Можеме да направиме реактивна постоечка вредност со тоа што ќе ја предадеме на методот ref()
:
Во Composition API, препорачаниот начин за декларирање на реактивна состојба е користење на функцијата
ref()
.
ref()
го зема аргументот и го враќа завиткан во објект ref со својство .value.
За да пристапите до refs во шаблонот на компонентата, декларирајте ги и вратете ги од функцијата
setup()
на компонентата.--
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
. Не е неопходно бидејќи нема двонасочно врзување - не го ажурираме насловот од клиентската страна, но претпочитам да го одржувам управувањето кохерентно за сите вредностиstate
Во овој момент, имаме реактивен модел од страна на клиентот.
<tbody> <tr is="vue:todo-line" v-for="todo in todos" :key="todo.id" :todo="todo"></tr> <!--1-2--> </tbody>
Todo
објектиis
е клучен за да се справиме со начинот на кој прелистувачот го анализира HTML. Видете за повеќе детали Сега можеме да имплементираме нова функција: да додадеме нова Todo
од клиентот. При кликнување на копчето Додај , ја читаме вредноста на полето Label , ги испраќаме податоците до API и го освежуваме моделот со одговорот.
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()
правилнаTodo
На страната HTML, додаваме копче и се врзуваме за функцијата create()
. Исто така, го додаваме полето Label и го врзуваме за моделот.
<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 ја врзува функцијата create()
со копчето HTML. Ја нарекува асинхроно и ја освежува реактивната листа Todo
со новата ставка вратена од повикот. Истото го правиме и за копчето Cleanup , за да ги отстраниме означените Todo
објекти.
Во овој пост, ги направив моите први чекори во зголемувањето на апликацијата SSR со Vue. Беше прилично директно. Најголемиот проблем на кој наидов беше Vue да го замени шаблонот за линија: не ја прочитав опширно документацијата и го пропуштив атрибутот is
.
Оди понатаму: