paint-brush
Vue.js によるクライアントの拡張@nfrankel
新しい歴史

Vue.js によるクライアントの拡張

Nicolas Fränkel17m2024/09/26
Read on Terminal Reader

長すぎる; 読むには

この投稿では、Vue を使用して SSR アプリを拡張する最初のステップを踏みました。これは非常に簡単でした。私が遭遇した最大の問題は、Vue が行テンプレートを置き換えることでした。ドキュメントを詳しく読んでいなかったため、is 属性を見逃していました。
featured image - Vue.js によるクライアントの拡張
Nicolas Fränkel HackerNoon profile picture
0-item


前回の投稿では、構築するための基礎を築きました。今こそ「本格的に」始めるときです。


私はVue.jsについてよく耳にしました。さらに、開発者からマネージャーに転向した友人が Vue について良いことを教えてくれたので、さらに興味が湧きました。私は Vue を見てみることにしました。これは、私が初心者の観点から学ぶ最初の「軽量」JavaScript フレームワークになります。

作業のレイアウト

前回の投稿では、WebJars と Thymeleaf について説明しました。サーバー側とクライアント側のセットアップは次のとおりです。

サーバー側

両方を 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>
  1. Spring Boot自体。通常の非リアクティブアプローチを採用することにしました。
  2. Spring Boot Thymeleaf 統合
  3. WebJars ロケータ。クライアント側で Vue のバージョンを指定する必要がないようにする。
  4. ついにVue!


Spring Boot 側では Kotlin Router と Bean DSL を使用しています。


 fun vue(todos: List<Todo>) = router { //1 GET("/vue") { ok().render("vue", mapOf("title" to "Vue.js", "todos" to todos)) //2-3 } }
  1. Todoオブジェクトの静的リストを渡す
  2. 以下を参照してください
  3. モデルをThymeleafに渡す


API の開発に慣れている方なら、 body()関数に馴染みがあるでしょう。この関数は、おそらく JSON 形式でペイロードを直接返します。render render()フローをビュー テクノロジ (この場合は Thymeleaf) に渡します。この関数は、次の 2 つのパラメータを受け入れます。


  1. ビューの名前。デフォルトでは、パスは/templatesで、プレフィックスは.htmlです。この場合、Thymeleaf は/templates/vue.htmlのビューを想定しています。
  2. キーと値のペアのモデルマップ

クライアント側

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>
  1. AxiosはHTTPリクエストの作成に役立ちます
  2. Vue自体
  3. クライアント側のコード
  4. データを設定する


先週の記事で説明したように、Thymeleaf の利点の 1 つは、静的ファイルのレンダリングとサーバー側のレンダリングの両方が可能であることです。この魔法を機能させるには、クライアント側のパス(つまりsrc ) とサーバー側のパス(つまりth:srcを指定します。


Vueコード

それでは、Vue コードについて詳しく見ていきましょう。

いくつかの機能を実装したいと考えています:


  1. ページが読み込まれると、すべてのTodo項目が表示されます。
  2. Todo完了チェックボックスをクリックすると、 completed属性を設定/解除する必要があります。
  3. クリーンアップボタンをクリックすると、完了したTodoがすべて削除されます。
  4. [追加]ボタンをクリックすると、次の値を持つTodo Todoリストに追加されます。
    • id : サーバー側で計算されたID。他のすべてのIDの最大値に1を加えたもの
    • label : labelラベルフィールドの値
    • completed : falseに設定

Vueへの第一歩

最初のステップはフレームワークをブートストラップすることです。上記でカスタムvue.jsファイルの参照をすでに設定しています。


 document.addEventListener('DOMContentLoaded', () => { //1 // The next JavaScript code snippets will be inside the block }
  1. DOMの読み込みが完了したらブロックを実行します


次のステップは、Vue にページの一部を管理させることです。HTML 側では、Vue が管理するトップレベルの部分を決定する必要があります。任意の<div>選択し、必要に応じて後で変更できます。


 <div id="app"> </div>


JavaScript 側では、前の HTML <div>の CSS セレクターを渡してappを作成します。


 Vue.createApp({}).mount('#app');


この時点では、ページが読み込まれると Vue が起動しますが、目に見えるようなことは何も起こりません。


次のステップは、Vueテンプレートを作成することです。Vue テンプレートは、Vue によって管理される通常の HTML <template>です。Vue は Javascript で定義できますが、HTML ページで行うことを好みます。


タイトルを表示できるルート テンプレートから始めましょう。


 <template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
  1. 簡単にバインドできるようにIDを設定する
  2. titleプロパティを使用します。設定が残っています


JavaScript 側では、管理コードを作成する必要があります。


 const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }
  1. HTMLテンプレートで使用されるtitleプロパティを宣言します。


最後に、アプリを作成するときにこのオブジェクトを渡す必要があります。


 Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
  1. コンポーネントを構成する
  2. Vueはrender()関数を必要とします
  3. ハイパースクリプトh()はオブジェクトとそのプロパティから仮想ノードを作成します。
  4. サーバー側で生成された値でtitleプロパティを初期化します


この時点で、Vue はタイトルを表示します。

基本的なやりとり

この時点で、ユーザーがチェックボックスをクリックしたときのアクションを実装できます。これは、サーバー側の状態で更新する必要があります。

まず、 Todoを表示するテーブル用に、新しいネストされた Vue テンプレートを追加しました。記事が長くなりすぎないように、詳細な説明は避けます。興味があれば、 ソース コードをご覧ください。


開始ライン テンプレートのコード (それぞれ 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>
  1. Todo IDを表示する
  2. Todoラベルを表示する
  3. 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 } }
  1. props配列を受け入れて、後でアクセスできるようにします
  2. Vueは呼び出しをトリガーしたeventを渡します
  3. AxiosはHTTP呼び出しを簡素化するJavaScriptライブラリです
  4. サーバー側で API を提供する必要があります。これはこの記事の範囲外ですが、ソース コードを自由に確認してください。
  5. JSONペイロード
  6. 定義されたすべての関数をHTMLからアクセスできるように返します

クライアント側モデル

前のセクションでは、2 つの間違いを犯しました。


  • ローカルモデルは管理していなかった
  • HTTPレスポンスのcallメソッドを使用しなかった


次の機能である、完了したタスクのクリーンアップを実装することでこれを実現します。


これで、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 } }
  1. 上記と同じ
  2. AxiosはHTTP呼び出しの自動JSON変換を提供します
  3. 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');
  1. 上記の説明に従って、Thymeleafを介してHTMLページに設定されたデータを取得します。
  2. titleの設定方法を変更します。双方向バインディングがないので、これは必要ではありません。クライアント側でタイトルを更新しませんが、すべての値にわたって処理の一貫性を保つことを好みます。
  3. Vueの期待通りにrefを返す
  4. ママ、JavaScriptのスプレッド演算子を使ってるんだよ
  5. stateからオブジェクトの属性を設定する


この時点で、リアクティブなクライアント側モデルが完成しました。


HTML 側では、関連する Vue 属性を使用します。


 <tbody> <tr is="vue:todo-line" v-for="todo in todos" :key="todo.id" :todo="todo"></tr> <!--1-2--> </tbody>
  1. Todoオブジェクトのリストをループする
  2. is属性は、ブラウザがHTMLを解析する方法に対処するために重要です。詳細については、 Vueのドキュメントを参照してください。

対応するテンプレートについては上記で説明しました。

モデルの更新

これで、クライアントから新しい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 } } }
  1. タイトルの周りに、関数に限定されたリアクティブラッパーを作成します。
  2. create()関数そのもの
  3. API呼び出しによって返された新しいJSONオブジェクトをTodoリストに追加します。
  4. フィールドの値をリセットする
  5. 削除時にリスト全体を置き換えます。仕組みは同じです


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属性を見逃していました。


しかし、HTTP 呼び出しを支援するために Axios を使用し、エラーを管理しなかったにもかかわらず、かなりの数の JavaScript 行を記述する必要がありました。


次の投稿では、同じ機能を Alpine.js で実装します。


この投稿の完全なソースコードは GitHub で見つかります:

さらに詳しく: