Во мојот претходен пост , поставив основа за да се изградам; сега е време да се започне „вистински“.
Слушнав многу Vue.js. Дополнително, еден пријател кој премина од програмер во менаџер ми кажа добри работи за Vue, што дополнително го разбуди мојот интерес. Решив да го погледнам: тоа ќе биде првата „лесна“ JavaScript рамка што ќе ја проучувам - од гледна точка на почетник, каков што сум јас.
Ги објаснив WebJars и Thymeleaf во последниот пост. Еве го поставувањето, серверот и клиентот.
Еве како ги интегрирам и двете во ПОМ:
<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>
Ги користам Kotlin Router и Bean DSL на страната на Spring Boot:
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
Еве го кодот од страната на HTML:
<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>
Како што беше објаснето во минатонеделната статија, една од придобивките на Thymeleaf е тоа што овозможува и статичко прикажување на датотеки и прикажување од страна на серверот. За да може магијата да функционира, јас одредувам патека од клиентот, т.е. src
и патека од страна на серверот, т.е. th:src
.
Сега, ајде да се нурнеме во кодот Vue.
Сакаме да имплементираме неколку карактеристики:
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. Шаблон Vue е обичен HTML <template>
управуван од Vue. Можете да го дефинирате Vue во Javascript, но јас претпочитам да го направам тоа на страницата HTML.
Да почнеме со root шаблон што може да го прикаже насловот.
<template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
title
; останува да се постави
На страната JavaScript, мора да го креираме управувачкиот код.
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 го прикажува насловот.
Во овој момент, можеме да го спроведеме дејството кога корисникот ќе кликне на полето за избор: тоа треба да се ажурира во состојба на серверот.
Прво, додадов нов вгнезден Vue шаблон за табелата што го прикажува Todo
. За да не го издолжам постот, ќе избегнам детално да го опишам. Ако сте заинтересирани, погледнете го изворниот код .
Еве го кодот на шаблонот за почетната линија, соодветно JavaScript и 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
атрибут е 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
што го активирал повикотВо претходниот дел, направив две грешки:
Тоа ќе го направиме со имплементирање на следната функција, а тоа е чистење на завршените задачи.
Сега знаеме како да се справиме со настаните преку Vue:
<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
Во овој момент, имаме реактивен модел од страна на клиентот.
На страната на HTML, ги користиме соодветните атрибути на Vue:
<tbody> <tr is="vue:todo-line" v-for="todo in todos" :key="todo.id" :todo="todo"></tr> <!--1-2--> </tbody>
Todo
објектиis
е клучен за да се справиме со начинот на кој прелистувачот го анализира HTML. Видете ја документацијата на Vue за повеќе деталиГо опишав соодветниот образец погоре.
Сега можеме да имплементираме нова функција: да додадеме нова 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
.
Сепак, морав да напишам неколку линии JavaScript, иако користев Axios за да ми помогне со HTTP повиците и не управував со грешките.
Во следниот пост, ќе ги имплементирам истите карактеристики со Alpine.js.
Целосниот изворен код за оваа објава може да се најде на GitHub:
Оди понатаму: