U svom prethodnom postu , postavio sam teren za izgradnju; sada je vrijeme da počnete "zaista".
Čuo sam dosta o Vue.js-u . Osim toga, prijatelj koji je prešao od programera do menadžera rekao mi je dobre stvari o Vue-u, što je dodatno pobudilo moje interesovanje. Odlučio sam da ga pogledam: to će biti prvi "laki" JavaScript framework koji ću proučiti - sa stanovišta početnika, što sam i jesam.
Objasnio sam WebJars i Thymeleaf u prošlom postu. Evo podešavanja, serverske i klijentske strane.
Evo kako integrišem oba u 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>
Koristim Kotlin Router i Bean DSL-ove na strani Spring Boot-a:
fun vue(todos: List<Todo>) = router { //1 GET("/vue") { ok().render("vue", mapOf("title" to "Vue.js", "todos" to todos)) //2-3 } }
Todo
objekata
Ako ste navikli da razvijate API-je, upoznati ste sa funkcijom body()
; direktno vraća korisni teret, vjerovatno u JSON formatu. render()
prosljeđuje tok tehnologiji prikaza, u ovom slučaju Thymeleaf. Prihvata dva parametra:
/templates
, a prefiks je .html
; u ovom slučaju, Thymeleaf očekuje pregled na /templates/vue.html
Evo koda na HTML strani:
<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>
Kao što je objašnjeno u prošlosedmičnom članku, jedna od prednosti Thymeleaf-a je ta što omogućava i statičko prikazivanje datoteka i renderiranje na strani servera. Da bi magija funkcionirala, specificiram putanju na strani klijenta, tj . src
, i putanju na strani servera, tj . th:src
.
Sada, zaronimo u Vue kod.
Želimo implementirati nekoliko funkcija:
Todo
stavkeTodo
, trebalo bi postaviti/poništiti completed
atributTodo
Todo
na listu Todo
sa sledećim vrednostima:id
: izračunati ID na strani servera kao maksimum svih ostalih ID-a plus 1label
: vrijednost polja Label za label
completed
: postavljeno na false
Prvi korak je pokretanje okvira. Već smo postavili referencu za našu prilagođenu datoteku vue.js
iznad.
document.addEventListener('DOMContentLoaded', () => { //1 // The next JavaScript code snippets will be inside the block }
Sljedeći korak je da dozvolite Vueu da upravlja dijelom stranice. Što se tiče HTML-a, moramo odlučiti kojim dijelom najvišeg nivoa Vue upravlja. Možemo odabrati proizvoljan <div>
i promijeniti ga kasnije ako je potrebno.
<div id="app"> </div>
Na strani JavaScripta, kreiramo aplikaciju , prosljeđujući CSS selektor prethodnog HTML-a <div>
.
Vue.createApp({}).mount('#app');
U ovom trenutku pokrećemo Vue kada se stranica učita, ali se ništa vidljivo ne dešava.
Sljedeći korak je kreiranje Vue predloška . Vue šablon je običan HTML <template>
kojim upravlja Vue. Možete definisati Vue u Javascript-u, ali ja radije to radim na HTML stranici.
Počnimo s korijenskim predloškom koji može prikazati naslov.
<template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
title
; ostaje da se postavi
Na strani JavaScripta, moramo kreirati upravljački kod.
const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }
title
, ono koje se koristi u HTML šablonu
Konačno, moramo proslijediti ovaj objekt kada kreiramo aplikaciju:
Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
render()
h()
za hiperscript kreira virtuelni čvor od objekta i njegovih svojstavatitle
vrijednošću generiranom na strani servera
U ovom trenutku, Vue prikazuje naslov.
U ovom trenutku možemo implementirati akciju kada korisnik klikne na potvrdni okvir: treba ga ažurirati u stanju na strani servera.
Prvo sam dodao novi ugniježđeni Vue predložak za tabelu koja prikazuje Todo
. Da ne bih produžio post, izbegavaću da ga detaljno opisujem. Ako ste zainteresovani, pogledajte izvorni kod .
Evo koda početne linije predloška, odnosno 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
idTodo
completed
atribut true
Vue dozvoljava rukovanje događajima putem @
sintakse.
<input type="checkbox" :checked="todo.completed" @click="check" />
Vue poziva funkciju check()
predloška kada korisnik klikne na liniju. Ovu funkciju definiramo u parametru 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
, kako bismo mu kasnije mogli pristupitievent
koji je pokrenuo pozivU prethodnom dijelu napravio sam dvije greške:
To ćemo učiniti implementacijom sljedeće funkcije, a to je čišćenje dovršenih zadataka.
Sada znamo kako upravljati događajima putem Vuea:
<button class="btn btn-warning" @click="cleanup">Cleanup</button>
Na objekt TodosApp
dodajemo funkciju istog imena:
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
je mjesto gdje pohranjujemo model
U semantici Vuea, Vue model je omot oko podataka za koje želimo da budu reaktivni . Reaktivno znači dvosmjerno povezivanje pogleda i modela. Postojeću vrijednost možemo učiniti reaktivnom tako što ćemo je proslijediti metodi ref()
:
U Composition API-ju, preporučeni način za deklariranje reaktivnog stanja je korištenje funkcije
ref()
.
ref()
uzima argument i vraća ga umotanog unutar ref objekta sa svojstvom .value.
Da biste pristupili referencama u šablonu komponente, deklarirajte ih i vratite ih iz funkcije
setup()
komponente.
uradimo to:
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
. Nije potrebno jer nema dvosmjernog povezivanja - ne ažuriramo naslov na strani klijenta, ali radije održavam koherentno rukovanje za sve vrijednostistate
U ovom trenutku imamo reaktivni model na strani klijenta.
Na strani HTML-a koristimo relevantne Vue atribute:
<tbody> <tr is="vue:todo-line" v-for="todo in todos" :key="todo.id" :todo="todo"></tr> <!--1-2--> </tbody>
Todo
objekatais
je ključan da se nosi sa načinom na koji pretraživač analizira HTML. Pogledajte Vue dokumentaciju za više detaljaGore sam opisao odgovarajući šablon.
Sada možemo implementirati novu funkciju: dodati novi Todo
od klijenta. Kada kliknemo na dugme Dodaj , čitamo vrijednost polja Label , šaljemo podatke API-ju i osvježavamo model odgovorom.
Evo ažuriranog koda:
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
Na HTML strani, dodajemo dugme i vezujemo se za funkciju create()
. Isto tako, dodajemo polje Label i povezujemo ga sa modelom.
<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 vezuje funkciju create()
za HTML dugme. Poziva ga asinhrono i osvježava reaktivnu Todo
listu novom stavkom koju je vratio poziv. Isto radimo za dugme Čišćenje , da uklonimo označene Todo
objekte.
Imajte na umu da nisam namjerno implementirao nikakav kod za rukovanje greškama kako bih izbjegao da kod bude složeniji nego što je potrebno. Ovdje ću se zaustaviti jer smo stekli dovoljno uvida za prvo iskustvo.
U ovom postu sam napravio prve korake u proširenju SSR aplikacije pomoću Vuea. Bilo je prilično jednostavno. Najveći problem na koji sam naišao bio je da Vue zamijeni predložak linije: nisam detaljno čitao dokumentaciju i propustio sam atribut is
.
Međutim, morao sam da napišem dosta redova JavaScript-a, iako sam koristio Axios da mi pomogne sa HTTP pozivima i nisam upravljao greškama.
U sljedećem postu implementirat ću iste karakteristike sa Alpine.js.
Kompletan izvorni kod za ovaj post možete pronaći na GitHubu:
Idi dalje: