في منشوري السابق ، وضعت الأساس للبناء عليه؛ والآن هو الوقت المناسب للبدء "بشكل حقيقي".
<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
إذا كنت معتادًا على تطوير واجهات برمجة التطبيقات، فأنت على دراية بوظيفة body()
؛ فهي تعيد الحمولة مباشرةً، ربما بتنسيق JSON. تمرر وظيفة render()
التدفق إلى تقنية العرض، في هذه الحالة، Thymeleaf. وهي تقبل معاملين:
/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 هي أنه يسمح بكل من عرض الملفات الثابتة والعرض من جانب الخادم. ولجعل السحر يعمل، أقوم بتحديد مسار من جانب العميل، أي src
، ومسار من جانب الخادم، أي th:src
.
Todo
Todo
، يجب تعيين/إلغاء تعيين سمة completed
"Todo
المكتملةTodo
إلى قائمة Todo
بالقيم التالية:id
: معرف محسوب من جانب الخادم باعتباره الحد الأقصى لجميع المعرفات الأخرى بالإضافة إلى 1label
: قيمة حقل التسمية 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 هو <template>
HTML عادي يتم إدارته بواسطة Vue. يمكنك تعريف 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
، تلك المستخدمة في قالب HTML
Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
render()
h()
لـ hyperscript بإنشاء عقدة افتراضية من الكائن وخصائصهtitle
بالقيمة التي تم إنشاؤها على جانب الخادم
أولاً، أضفت قالب Vue متداخلًا جديدًا للجدول الذي يعرض Todo
. لتجنب إطالة المنشور، سأتجنب وصفه بالتفصيل. إذا كنت مهتمًا، فقم بإلقاء نظرة على .
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
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()
تأخذ الوسيطة وتعيدها مغلفة داخل كائن ref مع خاصية .value.
للوصول إلى المراجع في قالب المكون، قم بإعلانها وإرجاعها من دالة
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
is ضرورية للتعامل مع الطريقة التي يحلل بها المتصفح HTML. راجع لمزيد من التفاصيل يمكننا الآن تنفيذ ميزة جديدة: إضافة Todo
جديدة من العميل. عند النقر فوق الزر "إضافة" ، نقرأ قيمة حقل التسمية ، ونرسل البيانات إلى واجهة برمجة التطبيقات، ونقوم بتحديث النموذج بالاستجابة.
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
.
اذهب أبعد من ذلك: