اذهب إلى المحتوى

إضافة تنسيق للمكونات واستعمال الخاصية computed في تطبيق Vue.js


Ola Abbas

لقد حان الوقت أخيرًا لجعل تطبيقنا يبدو أجمل قليلًا، إذ سنتعرّف في هذا المقال على الطرق المختلفة لتنسيق مكونات إطار العمل Vue باستخدام لغة CSS، كما سنضيف عدّادًا يعرض عدد عناصر المهام المكتملة باستخدام ميزة في إطار عمل Vue تسمّى الخاصية computed، إذ تعمل هذه الخاصية بصورة مشابهة للتوابع، ولكن يُعاد تشغيلها فقط عندما تتغير إحدى اعتمادياتها.

  • المتطلبات الأساسية: الإلمام بأساسيات لغات HTML وCSS وجافاسكربت JavaScript ومعرفة استخدام سطر الأوامر أو الطرفية، إذ تُكتَب مكونات Vue بوصفها مجموعةً من كائنات جافاسكربت التي تدير بيانات التطبيق وصيغة القوالب المستنِدة إلى لغة HTML المرتبطة مع بنية DOM الأساسية، كما ستحتاج إلى طرفية مثبَّت عليها node و npm لتثبيت Vue ولاستخدام بعض ميزاته الأكثر تقدمًا مثل مكونات الملف المفرد Single File Components أو دوال التصيير Render.
  • الهدف: التعرف على مكونات التنسيق وتعلّم كيفية استخدام خاصيات computed في Vue.

يجب إضافة شيفرة CSS الأساسية إلى تطبيقنا لجعله يبدو أفضل قبل إضافة مزيد من الميزات المتقدمة إليه، إذ يمتلك إطار العمل Vue ثلاث طرق شائعة لتنسيق التطبيقات:

  • ملفات CSS الخارجية.
  • التنسيقات العام في مكونات الملف المفرد، أي الملفات ذات اللاحقة ‎.vue.
  • التنسيقات على مستوى المكونات في مكونات الملف المفرد.

سنستخدِم مزيجًا من الطرق الثلاثة لإضفاء مظهر وتنسيق أفضل على تطبيقنا للتعرف على هذه الطرق.

التنسيق باستخدام ملفات CSS الخارجية

يمكنك تضمين ملفات CSS الخارجية وتطبيقها بطريقة عامة على تطبيقك، لذا أنشئ ملفًا بالاسم reset.css في المجلد src/assets، إذ تُعالَج الملفات الموجودة في هذا المجلد باستخدام Webpack، وبالتالي يمكننا استخدام معالجات CSS المسبقَة مثل SCSS أو معالجات ملحقَة مثل PostCSS. لن نستخدِم في هذا المقال مثل هذه الأدوات، ولكن يجب معرفة أنه ستُعالَج الشيفرة تلقائيًا عند تضمينها في المجلد assets.

أضِف المحتويات التالية في الملف reset.css:

/*reset.css*/
/* إعادة الضبط */
*,
*::before,
*::after {
  box-sizing: border-box;
}
*:focus {
  outline: 3px dashed #228bec;
}
html {
  font: 62.5% / 1.15 sans-serif;
}
h1,
h2 {
  margin-bottom: 0;
}
ul {
  list-style: none;
  padding: 0;
}
button {
  border: none;
  margin: 0;
  padding: 0;
  width: auto;
  overflow: visible;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: normal;
  -webkit-font-smoothing: inherit;
  -moz-osx-font-smoothing: inherit;
  -webkit-appearance: none;
}
button::-moz-focus-inner {
  border: 0;
}
button,
input,
optgroup,
select,
textarea {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
}
button,
input {
  /* 1 */
  overflow: visible;
}
input[type="text"] {
  border-radius: 0;
}
body {
  width: 100%;
  max-width: 68rem;
  margin: 0 auto;
  font: 1.6rem/1.25 "Helvetica Neue", Helvetica, Arial, sans-serif;
  background-color: #f5f5f5;
  color: #4d4d4d;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
}
@media screen and (min-width: 620px) {
  body {
    font-size: 1.9rem;
    line-height: 1.31579;
  }
}
/*نهاية إعادة الضبط*/

استورد بعد ذلك الملف reset.css في الملف src/main.js كما يلي:

import './assets/reset.css';

سيؤدي ذلك إلى نقل الملف أثناء خطوة البناء وإضافته تلقائيًا إلى موقعنا.

يجب تطبيق التنسيقات المُعاد ضبطها على التطبيق الآن، إذ تُظهر الصور التالية مظهر التطبيق قبل وبعد تطبيق إعادة الضبط.

قبل إعادة ضبط التنسيق:

01_todo-app-unstyled.png

بعد إعادة ضبط التنسيق:

02_todo-app-reset-styles.png

تشمل التغييرات الملحوظة إزالة نقط تعداد للقائمة وتغييرات لون الخلفية والتغييرات في الزر الأساسي وتنسيقات حقل الإدخال.

إضافة التنسيقات العامة إلى مكونات الملف المفرد

أعدنا ضبط شيفرة CSS لتكون موحدةًَ في المتصفحات ويجب الآن تخصيص التنسيقات، فهناك بعض التنسيقات التي نريد تطبيقها على مستوى المكونات في تطبيقنا من خلال إضافة التنسيقات إلى وسوم <style> في الملف App.vue بدلًا من إضافتها مباشرةً إلى ملف التنسيقات reset.css.

توجد مسبقًا بعض التنسيقات في الملف App.vue، فلنحذفها ونستبدلها بالتنسيقات التالية التي تضيف تنسيقًا إلى الأزرار وحقول الإدخال وتخصّص العنصر ‎#app وأبناءه، لذا عدِّل العنصر <style> في الملف App.vue بحيث يبدو كما يلي:

<style>
/* التنسيقات العامة */
.btn {
  padding: 0.8rem 1rem 0.7rem;
  border: 0.2rem solid #4d4d4d;
  cursor: pointer;
  text-transform: capitalize;
}
.btn__danger {
  color: #fff;
  background-color: #ca3c3c;
  border-color: #bd2130;
}
.btn__filter {
  border-color: lightgrey;
}
.btn__danger:focus {
  outline-color: #c82333;
}
.btn__primary {
  color: #fff;
  background-color: #000;
}
.btn-group {
  display: flex;
  justify-content: space-between;
}
.btn-group > * {
  flex: 1 1 auto;
}
.btn-group > * + * {
  margin-left: 0.8rem;
}
.label-wrapper {
  margin: 0;
  flex: 0 0 100%;
  text-align: center;
}
[class*="__lg"] {
  display: inline-block;
  width: 100%;
  font-size: 1.9rem;
}
[class*="__lg"]:not(:last-child) {
  margin-bottom: 1rem;
}
@media screen and (min-width: 620px) {
  [class*="__lg"] {
    font-size: 2.4rem;
  }
}
.visually-hidden {
  position: absolute;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(1px 1px 1px 1px);
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: rect(1px, 1px, 1px, 1px);
  white-space: nowrap;
}
[class*="stack"] > * {
  margin-top: 0;
  margin-bottom: 0;
}
.stack-small > * + * {
  margin-top: 1.25rem;
}
.stack-large > * + * {
  margin-top: 2.5rem;
}
@media screen and (min-width: 550px) {
  .stack-small > * + * {
    margin-top: 1.4rem;
  }
  .stack-large > * + * {
    margin-top: 2.8rem;
  }
}
/* نهاية التنسيقات العامة */
#app {
  background: #fff;
  margin: 2rem 0 4rem 0;
  padding: 1rem;
  padding-top: 0;
  position: relative;
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1);
}
@media screen and (min-width: 550px) {
  #app {
    padding: 4rem;
  }
}
#app > * {
  max-width: 50rem;
  margin-left: auto;
  margin-right: auto;
}
#app > form {
  max-width: 100%;
}
#app h1 {
  display: block;
  min-width: 100%;
  width: 100%;
  text-align: center;
  margin: 0;
  margin-bottom: 1rem;
}
</style>

إذا اختبرت التطبيق، فسترى أنّ قائمة المهام موجودة ضمن قسم خاص به الآن مع بعض التنسيقات الأفضل لعناصر المهام كما يلي:

03_todo-app-partial-styles.png

إضافة أصناف CSS في إطار العمل Vue

سنطبّق أصناف CSS على عنصر الزر <button> في المكوِّن ToDoForm، وبما أنّ قوالب Vue تستخدِم لغة HTML، فسنستخدِم الطريقة نفسها عن طريق إضافة السمة class=""‎ إلى العنصر، لذا أضِف السمة class="btn btn__primary btn__lg"‎ إلى العنصر <button> في نموذجك:

<button type="submit" class="btn btn__primary btn__lg">
  Add
</button>

هناك تغيير آخر يمكن إجراؤه، فبما أنّ النموذج يشير إلى قسم معيّن من الصفحة، فيمكن استخدام العنصر <h2>، ولكن يشير العنصر label إلى الغرض من النموذج مسبقًا، لذلك يجب تجنب التكرار من خلال تغليف العنصر label ضمن العنصر <h2>، كما يمكننا إضافة بعض تنسيقات CSS العامة الأخرى، إذ سنضيف الصنف input__lg إلى العنصر <input>، لذا عدِّل قالب المكوّن ToDoForm بحيث يبدو كما يلي:

<template>
  <form @submit.prevent="onSubmit">
    <h2 class="label-wrapper">
      <label for="new-todo-input" class="label__lg">
        What needs to be done?
      </label>
    </h2>
    <input
      type="text"
      id="new-todo-input"
      name="new-todo"
      autocomplete="off"
      v-model.lazy.trim="label"
      class="input__lg"
    />
    <button type="submit" class="btn btn__primary btn__lg">
      Add
    </button>
  </form>
</template>

كما سنضيف الصنف stack-large إلى الوسم <ul> في الملف App.vue كما يلي، مما يساعد في تحسين التباعد بين عناصر المهام:

<ul aria-labelledby="list-summary" class="stack-large">

إضافة التنسيقات ذات النطاق المحدد

نريد الآن تنسيق المكوِِّن ToDoItem، إذ يمكننا إضافة العنصر <style> ضمنه لتكون تعريفات التنسيقات قريبةً من المكوِّن، لكن إذا غيرت هذه التنسيقات أشياءً خارج هذا المكوِّن، فسيكون تعقّب التنسيقات المسؤولة عن ذلك وإصلاح المشكلة أمرًا صعبًا.

سنستخدِم السمة scoped حيث يرتبط محدِّد سمة HTML الفريدة data مع جميع التنسيقات، مما يمنعها من التعارض على المستوى العام، كما يمكنك استخدام معدِّل scoped من خلال إنشاء العنصر <style> ضمن المكوِّن ToDoItem.vue، ثم منحه السمة scoped كما يلي:

<style scoped>
</style>

انسخ بعد ذلك شيفرة CSS التالية والصقها في العنصر <style> الذي أنشأناه للتو:

.custom-checkbox > .checkbox-label {
  font-family: Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-weight: 400;
  font-size: 16px;
  font-size: 1rem;
  line-height: 1.25;
  color: #0b0c0c;
  display: block;
  margin-bottom: 5px;
}
.custom-checkbox > .checkbox {
  font-family: Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-weight: 400;
  font-size: 16px;
  font-size: 1rem;
  line-height: 1.25;
  box-sizing: border-box;
  width: 100%;
  height: 40px;
  height: 2.5rem;
  margin-top: 0;
  padding: 5px;
  border: 2px solid #0b0c0c;
  border-radius: 0;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}
.custom-checkbox > input:focus {
  outline: 3px dashed #fd0;
  outline-offset: 0;
  box-shadow: inset 0 0 0 2px;
}
.custom-checkbox {
  font-family: Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  font-weight: 400;
  font-size: 1.6rem;
  line-height: 1.25;
  display: block;
  position: relative;
  min-height: 40px;
  margin-bottom: 10px;
  padding-left: 40px;
  clear: left;
}
.custom-checkbox > input[type="checkbox"] {
  -webkit-font-smoothing: antialiased;
  cursor: pointer;
  position: absolute;
  z-index: 1;
  top: -2px;
  left: -2px;
  width: 44px;
  height: 44px;
  margin: 0;
  opacity: 0;
}
.custom-checkbox > .checkbox-label {
  font-size: inherit;
  font-family: inherit;
  line-height: inherit;
  display: inline-block;
  margin-bottom: 0;
  padding: 8px 15px 5px;
  cursor: pointer;
  touch-action: manipulation;
}
.custom-checkbox > label::before {
  content: "";
  box-sizing: border-box;
  position: absolute;
  top: 0;
  left: 0;
  width: 40px;
  height: 40px;
  border: 2px solid currentColor;
  background: transparent;
}
.custom-checkbox > input[type="checkbox"]:focus + label::before {
  border-width: 4px;
  outline: 3px dashed #228bec;
}
.custom-checkbox > label::after {
  box-sizing: content-box;
  content: "";
  position: absolute;
  top: 11px;
  left: 9px;
  width: 18px;
  height: 7px;
  transform: rotate(-45deg);
  border: solid;
  border-width: 0 0 5px 5px;
  border-top-color: transparent;
  opacity: 0;
  background: transparent;
}
.custom-checkbox > input[type="checkbox"]:checked + label::after {
  opacity: 1;
}
@media only screen and (min-width: 40rem) {
  label,
  input,
  .custom-checkbox {
    font-size: 19px;
    font-size: 1.9rem;
    line-height: 1.31579;
  }
}

يجب الآن إضافة أصناف CSS إلى القالب لربط التنسيقات، لذا انتقل إلى العنصر <div> الجذر ثم أضِف الصنف custom-checkbox، وأضف الصنف checkbox إلى العنصر <input> ثم أضف الصنف إلى العنصر <label>، وبالتالي سيحتوي التطبيق على مربعات اختيار مخصَّصة، كما يجب أن يبدو تطبيقك الآن كما يلي:

04_todo-app-complete-styles.png

انتهينا من تنسيق تطبيقنا، وسنعود الآن إلى إضافة مزيد من الوظائف إليه، أي استخدام الخاصية computed لإضافة عدد عناصر المهام المكتملة إلى تطبيقنا.

استخدام الخاصية computed في إطار العمل Vue

نريد إضافة عدّاد counter يلخّص عدد العناصر في قائمة المهام، مما يساعد التقنيات المساعدة في معرفة عدد المهام المكتملة، فإذا كان لدينا عنصران مكتملان من أصل خمسة في قائمة المهام، فسنرى العبارة "2‎ items completed out of 5" كما يلي:

<h2>{{ToDoItems.filter(item => item.done).length}} out of {{ToDoItems.length}} items completed</h2>

سيُعاد حساب هذا العدد في كل عملية تصيير، ويمكن أن يكون ذلك غير مهم بالنسبة إلى تطبيق صغير مثل تطبيقنا، ولكنه سيتسبب في مشكلة خطيرة في الأداء بالنسبة للتطبيقات الأكبر حجمًا أو عندما يكون التعبير أكثر تعقيدًا، لذا يكون الحل الأفضل هو استخدام خاصيات computed في إطار العمل Vue، إذ تعمل هذه الخاصيات بصورة مشابهة للتوابع، ولكن يُعاد تشغيلها فقط عندما تتغير إحدى اعتمادياتها، إذ سيُعاد تشغيلها في حالتنا عندما تتغير المصفوفة ToDoItems فقط.

يمكن إنشاء الخاصية computed من خلال إضافتها إلى كائن المكوِّن مثل الخاصية methods التي استخدمناها سابقًا.

إضافة عداد

أضِف الشيفرة البرمجية التالية إلى كائن المكوِّن App بعد الخاصية methods، إذ يأخذ التابع listSummary()‎ عدد عناصر ToDoItems المنتهية، ويعيد سلسلةً نصيةً تمثّل ذلك.

computed: {
  listSummary() {
    const numberFinishedItems = this.ToDoItems.filter(item =>item.done).length
    return `${numberFinishedItems} out of ${this.ToDoItems.length} items completed`
  }
}

يمكننا الآن إضافة {{listSummary}} مباشرةً إلى نموذجنا، إذ سنضيفه ضمن العنصر <h2> قبل العنصر <ul> مباشرةً، كما سنضيف السمة id والسمة aria-labelledby لتكون محتويات العنصر <h2> عنوانًا label للعنصر <ul>، لذا أضِف العنصر <h2> وعدّل العنصر <ul> ضمن قالب المكون App كما يلي:

<h2 id="list-summary">{{listSummary}}</h2>
<ul aria-labelledby="list-summary" class="stack-large">
  <li v-for="item in ToDoItems" :key="item.id">
    <to-do-item :label="item.label" :done="item.done" :id="item.id"></to-do-item>
  </li>
</ul>

يجب أن تشاهد الآن ملخص القائمة في تطبيقك مع تحديث عدد العناصر الإجمالي كلما أضفت مزيدًا من عناصر المهام، ولكن إذا حاولت تحديد بعض العناصر ثم إلغاء تحديدها، فسيظهر خطأ لأننا لم نتعقّب بعد البيانات "المكتملة" فعليًا، لذلك لن يتغير عدد العناصر المكتملة.

تعقب تغيرات عدد العناصر المكتملة

يمكننا استخدام الأحداث لالتقاط تحديث مربع الاختيار وإدارة القائمة وفقًا لذلك، كما يمكننا ربط معالج الحدث ‎@change مع كل مربع اختيار بدلًا من استخدام الموجِّه v-model لأننا لا نعتمد على ضغط الزر لبدء التغيير.

عدّل العنصر <input> في المكون ToDoItem.vue ليبدو كما يلي، كما يمكننا تضمين ‎$emit()‎ لأنّ كل ما يجب فعله هو إرسال أن مربع الاختيار محدَّد فقط:

<input type="checkbox" class="checkbox" :id="id" :checked="isDone"
       @change="$emit('checkbox-changed')" />

أضف تابعًا جديدًا بالاسم updateDoneStatus()‎ في المكوِّن App.vue بعد التابع addToDo()‎، إذ يجب أن يأخذ هذا التابع معامِلًا واحدًا هو معرّف عنصر المهمة، كما نريد العثور على العنصر ذي المعرّف id المطابق وتحديث حالته done لتكون معاكسةً لحالته الحالية:

updateDoneStatus(toDoId) {
  const toDoToUpdate = this.ToDoItems.find(item => item.id === toDoId)
  toDoToUpdate.done = !toDoToUpdate.done
}

يجب تشغيل هذا التابع كلما أصدر المكوِّن ToDoItem الحدث checkbox-changed، وتمرير معرّفه item.id بوصفه معاملًا، لذا عدّل الاستدعاء <to-do-item></to-do-item> كما يلي:

<to-do-item :label="item.label" :done="item.done" :id="item.id"
            @checkbox-changed="updateDoneStatus(item.id)">
</to-do-item>

إذا اختبرت العنصر ToDoItem الآن، فيجب أن تشاهد تحديث العدّاد الملخِّص بطريقة صحيحة.

05_todo-counter.png

الخلاصة

تعرّفنا في هذا المقال على تنسيق مكوّنات إطار العمل Vue باستخدام لغة CSS، واستخدمنا الخاصية computed لإضافة ميزة مفيدة إلى تطبيقنا وهي حساب عدد عناصر المهام المكتملة، وسنتعرّف في المقال التالي على التصيير أو العرض الشرطي Conditional Rendering وكيفية استخدامه لإظهار نموذج التعديل عندما نريد تعديل عناصر المهام الحالية.

ترجمة -وبتصرّف- للمقالين Styling Vue components with CSS وUsing Vue computed properties.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...