前回の投稿では、構築するための基礎を築きました。今こそ「本格的に」始めるときです。
<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 render()
フローをビュー テクノロジ (この場合は Thymeleaf) に渡します。この関数は、次の 2 つのパラメータを受け入れます。
/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 の利点の 1 つは、静的ファイルのレンダリングとサーバー側のレンダリングの両方が可能であることです。この魔法を機能させるには、クライアント側のパス(つまりsrc
) とサーバー側のパス(つまりth:src
を指定します。
Todo
項目が表示されます。Todo
完了チェックボックスをクリックすると、 completed
属性を設定/解除する必要があります。Todo
がすべて削除されます。Todo
Todo
リストに追加されます。id
: サーバー側で計算されたID。他のすべてのIDの最大値に1を加えたもの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 側では、前の HTML <div>
の CSS セレクターを渡してappを作成します。
Vue.createApp({}).mount('#app');
次のステップは、Vueテンプレートを作成することです。Vue テンプレートは、Vue によって管理される通常の HTML <template>
です。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
プロパティを宣言します。
Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
render()
関数を必要としますh()
はオブジェクトとそのプロパティから仮想ノードを作成します。title
プロパティを初期化します
まず、 Todo
を表示するテーブル用に、新しいネストされた Vue テンプレートを追加しました。記事が長くなりすぎないように、詳細な説明は避けます。興味があれば、 をご覧ください。
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
IDを表示するTodo
ラベルを表示する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()
引数を受け取り、それを .value プロパティを持つ ref オブジェクト内にラップして返します。
コンポーネントのテンプレート内の参照にアクセスするには、コンポーネントの
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
追加するという新しい機能を実装できるようになりました。 [追加]ボタンをクリックすると、ラベルフィールドの値が読み取られ、データが 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
オブジェクトを削除します。
この投稿では、Vue を使用して SSR アプリを拡張する最初のステップを踏みました。これは非常に簡単でした。私が遭遇した最大の問題は、Vue が行テンプレートを置き換えることでした。ドキュメントを詳しく読んでいなかったため、 is
属性を見逃していました。
さらに詳しく: