Dalam catatan saya sebelum ini , saya meletakkan asas untuk membina; sekarang adalah masa untuk memulakan "sebenarnya".
Saya mendengar banyak Vue.js . Selain itu, rakan yang beralih daripada pembangun kepada pengurus memberitahu saya perkara yang baik tentang Vue, yang seterusnya menarik minat saya. Saya memutuskan untuk melihatnya: ia akan menjadi rangka kerja JavaScript "ringan" pertama yang akan saya kaji - dari sudut pandangan seorang pemula, iaitu saya.
Meletakkan kerja
Saya menerangkan WebJars dan Thymeleaf dalam jawatan terakhir. Berikut ialah persediaan, bahagian pelayan dan pelanggan.
Bahagian pelayan
Inilah cara saya mengintegrasikan kedua-duanya dalam 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 itu sendiri; Saya memutuskan pendekatan biasa, tidak reaktif
- Penyepaduan Spring Boot Thymeleaf
- WebJars locator, untuk mengelak daripada menentukan versi Vue pada bahagian klien
- Vue, akhirnya!
Saya menggunakan Penghala Kotlin dan DSL Bean pada bahagian Spring Boot:
fun vue(todos: List<Todo>) = router { //1 GET("/vue") { ok().render("vue", mapOf("title" to "Vue.js", "todos" to todos)) //2-3 } }
- Lulus senarai statik objek
Todo
- Lihat di bawah
- Hantar model kepada Thymeleaf
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:
- Nama pandangan. Secara lalai, laluannya ialah
/templates
dan awalan ialah.html
; dalam kes ini, Thymeleaf menjangkakan paparan di/templates/vue.html
- Peta model pasangan nilai kunci
Pihak pelanggan
Berikut ialah kod di sebelah 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 membantu membuat permintaan HTTP
- Vue sendiri
- Kod pihak pelanggan kami
- Tetapkan data
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
.
Kod Vue
Sekarang, mari kita selami kod Vue.
Kami ingin melaksanakan beberapa ciri:
- Selepas halaman dimuatkan, halaman harus memaparkan semua item
Todo
- Apabila mengklik pada kotak semak
Todo
complete, ia harus menetapkan/menyahset atributcompleted
- Apabila mengklik pada butang Pembersihan , ia memadam semua
Todo
yang telah selesai - Apabila mengklik pada butang Tambah , ia harus menambah
Todo
ke senaraiTodo
dengan nilai berikut:-
id
: ID pengiraan sebelah pelayan sebagai maksimum semua ID lain tambah 1 -
label
: nilai medan Label untuklabel
-
completed
: ditetapkan kepadafalse
-
Langkah pertama kami ke Vue
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 }
- Jalankan blok apabila DOM telah selesai dimuatkan
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');
Pada ketika ini, kami melancarkan Vue apabila halaman dimuatkan, tetapi tiada apa yang kelihatan berlaku.
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.
Mari kita mulakan dengan templat akar yang boleh memaparkan tajuk.
<template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
- Tetapkan ID agar mudah diikat
- Gunakan harta
title
; ia masih perlu disediakan
Di sisi JavaScript, kita mesti mencipta kod pengurusan.
const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }
- Isytiharkan harta
title
, yang digunakan dalam templat HTML
Akhir sekali, kita mesti lulus objek ini apabila kita mencipta aplikasi:
Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
- Konfigurasikan komponen
- Vue menjangkakan fungsi
render()
-
h()
untuk hiperskrip mencipta nod maya daripada objek dan sifatnya - Mulakan sifat
title
dengan bahagian pelayan yang dijana nilai
Pada ketika ini, Vue memaparkan tajuk.
Interaksi asas
Pada ketika ini, kami boleh melaksanakan tindakan apabila pengguna mengklik pada kotak pilihan: ia perlu dikemas kini dalam keadaan sebelah pelayan.
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 kod sumber .
Berikut ialah kod templat baris permulaan, masing-masing JavaScript dan 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>
- Paparkan id
Todo
- Paparkan label
Todo
- Tandai kotak jika atribut
completed
adalahtrue
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 } }
- Terima tatasusunan
props
, supaya kami boleh mengaksesnya kemudian - Vue melepasi
event
yang mencetuskan panggilan - Axios ialah lib JavaScript yang memudahkan panggilan HTTP
- Bahagian pelayan mesti menyediakan API; ia di luar skop siaran ini, tetapi jangan ragu untuk menyemak kod sumber.
- Muatan JSON
- Kami mengembalikan semua fungsi yang ditentukan untuk menjadikannya boleh diakses daripada HTML
Model sisi pelanggan
Dalam bahagian sebelumnya, saya membuat dua kesilapan:
- Saya tidak menguruskan mana-mana model tempatan
- Saya tidak menggunakan kaedah panggilan respons HTTP
Kami akan melakukannya dengan melaksanakan ciri seterusnya, iaitu pembersihan tugasan yang telah selesai.
Kami kini tahu cara mengendalikan acara melalui Vue:
<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 } }
- Seperti di atas
- Axios menawarkan penukaran JSON automatik bagi panggilan HTTP
-
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.
Jom buat:
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');
- Dapatkan set data dalam halaman HTML, melalui Thymeleaf, seperti yang dijelaskan di atas
- Kami mengubah cara kami menetapkan
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 nilai - Kembalikan rujukan, mengikut jangkaan Vue
- Lihat, ma, saya menggunakan operator spread JavaScript
- Konfigurasikan objek yang diatributkan daripada
state
Pada ketika ini, kami mempunyai model sisi pelanggan yang reaktif .
Di bahagian HTML, kami menggunakan atribut Vue yang berkaitan:
<tbody> <tr is="vue:todo-line" v-for="todo in todos" :key="todo.id" :todo="todo"></tr> <!--1-2--> </tbody>
- Gelung pada senarai objek
Todo
- Atribut
is
adalah penting untuk mengatasi cara pelayar menghuraikan HTML. Lihat dokumentasi Vue untuk butiran lanjut
Saya telah menerangkan templat yang sepadan di atas.
Mengemas kini model
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.
Berikut ialah kod yang dikemas kini:
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 } } }
- Buat pembalut reaktif di sekeliling tajuk yang skopnya terhad kepada fungsi
- Fungsi
create()
betul - Tambahkan objek JSON baharu yang dikembalikan oleh panggilan API ke senarai
Todo
- Tetapkan semula nilai medan
- Gantikan keseluruhan senarai apabila memadam; mekanismenya adalah sama
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.
Ambil perhatian bahawa saya tidak sengaja melaksanakan sebarang kod pengendalian ralat untuk mengelakkan membuat kod lebih kompleks daripada yang diperlukan. Saya akan berhenti di sini kerana kami memperoleh cukup cerapan untuk pengalaman pertama.
Kesimpulan
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
.
Walau bagaimanapun, saya terpaksa menulis beberapa baris JavaScript, walaupun saya menggunakan Axios untuk membantu saya dengan panggilan HTTP dan tidak menguruskan ralat.
Dalam siaran seterusnya, saya akan melaksanakan ciri yang sama dengan Alpine.js.
Kod sumber lengkap untuk siaran ini boleh didapati di GitHub:
Pergi lebih jauh: