في منشوري السابق ، وضعت الأساس للبناء عليه؛ والآن هو الوقت المناسب للبدء "بشكل حقيقي".
سمعت الكثير عن Vue.js. بالإضافة إلى ذلك، أخبرني أحد الأصدقاء الذي تحول من مطور إلى مدير بأشياء جيدة عن 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>
أستخدم Kotlin Router وBean DSLs على جانب 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
إذا كنت معتادًا على تطوير واجهات برمجة التطبيقات، فأنت على دراية بوظيفة 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
: معرف محسوب من جانب الخادم باعتباره الحد الأقصى لجميع المعرفات الأخرى بالإضافة إلى 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. قالب Vue هو <template>
HTML عادي يتم إدارته بواسطة Vue. يمكنك تعريف Vue في Javascript، لكنني أفضل القيام بذلك على صفحة HTML.
لنبدأ بقالب جذر يمكنه عرض العنوان.
<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()
لـ hyperscript بإنشاء عقدة افتراضية من الكائن وخصائصه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
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
الذي أدى إلى المكالمةفي القسم السابق، ارتكبت خطأين:
سنقوم بذلك من خلال تنفيذ الميزة التالية، وهي تنظيف المهام المكتملة.
نحن نعلم الآن كيفية التعامل مع الأحداث عبر 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.
للوصول إلى المراجع في قالب المكون، قم بإعلانها وإرجاعها من دالة
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
is ضرورية للتعامل مع الطريقة التي يحلل بها المتصفح HTML. راجع وثائق Vue لمزيد من التفاصيللقد وصفت القالب المقابل أعلاه.
يمكننا الآن تنفيذ ميزة جديدة: إضافة 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
.
ومع ذلك، كان عليّ كتابة عدد لا بأس به من أسطر JavaScript، على الرغم من أنني استخدمت Axios لمساعدتي في استدعاءات HTTP ولم أتمكن من إدارة الأخطاء.
في المنشور التالي، سأقوم بتنفيذ نفس الميزات مع Alpine.js.
يمكن العثور على الكود المصدر الكامل لهذه التدوينة على GitHub:
اذهب أبعد من ذلك: