Dalam catatan saya sebelum ini , saya meletakkan asas untuk membina; sekarang adalah masa untuk memulakan "sebenarnya".
<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
Jika anda biasa membangunkan API, anda sudah biasa dengan fungsi body()
; ia mengembalikan muatan secara langsung, mungkin dalam format JSON. render()
menghantar aliran ke teknologi paparan, dalam kes ini, Thymeleaf. Ia menerima dua parameter:
/templates
dan awalan ialah .html
; dalam kes ini, Thymeleaf menjangkakan paparan di /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>
Seperti yang dijelaskan dalam artikel minggu lepas, salah satu faedah Thymeleaf ialah ia membenarkan pemaparan fail statik dan pemaparan sebelah pelayan. Untuk membuat keajaiban berfungsi, saya menentukan laluan sisi klien, iaitu , src
dan laluan sisi pelayan, iaitu , th:src
.
Todo
Todo
complete, ia harus menetapkan/menyahset atribut completed
Todo
yang telah selesaiTodo
ke senarai Todo
dengan nilai berikut:id
: ID pengiraan sebelah pelayan sebagai maksimum semua ID lain tambah 1label
: nilai medan Label untuk label
completed
: ditetapkan kepada false
Langkah pertama ialah bootstrap rangka kerja. Kami telah menyediakan rujukan untuk fail vue.js
tersuai kami di atas.
document.addEventListener('DOMContentLoaded', () => { //1 // The next JavaScript code snippets will be inside the block }
Langkah seterusnya ialah membenarkan Vue mengurus sebahagian halaman. Dari segi HTML, kita mesti memutuskan bahagian peringkat teratas yang Vue uruskan. Kita boleh memilih <div>
sewenang-wenangnya dan mengubahnya kemudian jika perlu.
<div id="app"> </div>
Di sisi JavaScript, kami mencipta apl , melepasi pemilih CSS bagi HTML sebelumnya <div>
.
Vue.createApp({}).mount('#app');
Langkah seterusnya ialah membuat templat Vue . Templat Vue ialah <template>
HTML biasa yang diuruskan oleh Vue. Anda boleh menentukan Vue dalam Javascript, tetapi saya lebih suka melakukannya pada halaman HTML.
<template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
title
; ia masih perlu disediakan
const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }
title
, yang digunakan dalam templat HTML
Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
render()
h()
untuk hiperskrip mencipta nod maya daripada objek dan sifatnyatitle
dengan bahagian pelayan yang dijana nilai
Mula-mula, saya menambah templat Vue bersarang baharu untuk jadual yang memaparkan Todo
. Untuk mengelak daripada memanjangkan siaran, saya akan mengelak daripada menerangkannya secara terperinci. Jika anda berminat, sila lihat .
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
adalah true
Vue membenarkan pengendalian acara melalui sintaks @
.
<input type="checkbox" :checked="todo.completed" @click="check" />
Vue memanggil fungsi check()
templat apabila pengguna mengklik pada baris. Kami mentakrifkan fungsi ini dalam parameter 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
, supaya kami boleh mengaksesnya kemudianevent
yang mencetuskan panggilan
<button class="btn btn-warning" @click="cleanup">Cleanup</button>
Pada objek TodosApp
, kami menambah fungsi dengan nama yang sama:
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
adalah tempat kami menyimpan model
Dalam semantik Vue, model Vue ialah pembalut di sekeliling data yang kita mahu reaktif . Reaktif bermaksud pengikatan dua hala antara pandangan dan model. Kita boleh membuat nilai sedia ada reaktif dengan menghantarnya ke kaedah ref()
:
Dalam API Komposisi, cara yang disyorkan untuk mengisytiharkan keadaan reaktif adalah menggunakan fungsi
ref()
.
ref()
mengambil hujah dan mengembalikannya dibalut dalam objek ref dengan sifat .value.
Untuk mengakses rujukan dalam templat komponen, isytiharkan dan kembalikannya daripada fungsi
setup()
komponen.--
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
. Ia tidak perlu kerana tiada pengikatan dua hala - kami tidak mengemas kini tajuk bahagian klien, tetapi saya lebih suka mengekalkan pengendalian yang koheren merentas semua nilaistate
Pada ketika ini, kami mempunyai model sisi pelanggan yang reaktif .
<tbody> <tr is="vue:todo-line" v-for="todo in todos" :key="todo.id" :todo="todo"></tr> <!--1-2--> </tbody>
Todo
is
adalah penting untuk mengatasi cara pelayar menghuraikan HTML. Lihat untuk butiran lanjut Kami kini boleh melaksanakan ciri baharu: tambah Todo
baharu daripada klien. Apabila mengklik pada butang Tambah , kami membaca nilai medan Label , menghantar data ke API, dan menyegarkan model dengan respons.
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()
betulTodo
Di sebelah HTML, kami menambah butang dan mengikat kepada fungsi create()
. Begitu juga, kami menambah medan Label dan mengikatnya pada 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 mengikat fungsi create()
pada butang HTML. Ia memanggilnya secara tidak segerak dan menyegarkan senarai Todo
reaktif dengan item baharu yang dikembalikan melalui panggilan. Kami melakukan perkara yang sama untuk butang Pembersihan , untuk mengalih keluar objek Todo
yang diperiksa.
Dalam siaran ini, saya mengambil langkah pertama saya dalam menambah apl SSR dengan Vue. Ia agak mudah. Isu terbesar yang saya temui ialah untuk Vue menggantikan templat baris: Saya tidak membaca dokumentasi secara meluas dan terlepas atribut is
.
Pergi lebih jauh: