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

التفاعلية ودورة الحياة وسهولة وصول المستخدمين في إطار عمل Svelte


Ola Abbas

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

  • المتطلبات الأساسية: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة باستخدام سطر الأوامر أو الطرفية، وستحتاج طرفية مثبَّت عليها node و npm لتصريف وبناء تطبيقك.
  • الهدف: تعلّم بعض تقنيات Svelte المتقدمة التي تتضمن حل مشاكل التفاعل ومشاكل سهولة الوصول لمستخدِمي لوحة المفاتيح المتعلقة بدورة حياة المكونات وغير ذلك.

سنركز على بعض مشاكل سهولة الوصول التي تتضمن إدارة التركيز، إذ سنستخدِم بعض التقنيات للوصول إلى عُقد نموذج DOM وتنفيذ توابع مثل التابعَين focus()‎ و select()‎، كما سنرى كيفية التصريح عن تنظيف مستمعي الأحداث على عناصر DOM، كما سنتعلّم بعض الأمور عن دورة حياة المكونات لفهم متى تُثبَّت عُقد DOM ومتى تُفصَل من نموذج DOM وكيف يمكننا الوصول إليها، كما سنتعرف على الموجه action الذي سيسمح بتوسيع وظائف عناصر HTML بطريقة قابلة لإعادة الاستخدام والتصريح.

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

سنطوّر المكونات الجديدة التالية خلال هذا المقال:

  • MoreActions: يعرض الزرين "تحديد الكل Check All" و"حذف المهام المكتملة Remove Completed" ويصدر الأحداث المقابلة المطلوبة للتعامل مع وظائفهما.
  • NewTodo: يعرض حقل الإدخال <input> وزر "الإضافة Add" لإضافة مهمة جديدة.
  • TodosStatus: عرض عنوان الحالة "x out of y items completed" التي تمثِّل المهام المكتملة.

يمكن متابعة كتابة شيفرتك معنا، لذلك انسخ أولًا مستودع github -إذا لم تفعل ذلك مسبقًا- باستخدام الأمر التالي:

git clone https://github.com/opensas/mdn-svelte-tutorial.git

ثم يمكنك الوصول إلى حالة التطبيق الحالية من خلال تشغيل الأمر التالي:

cd mdn-svelte-tutorial/05-advanced-concepts

أو يمكنك تنزيل محتوى المجلد مباشرةً كما يلي:

npx degit opensas/mdn-svelte-tutorial/05-advanced-concepts

تذكَّر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير، فإذا أردت متابعتنا، فابدأ بكتابة الشيفرة باستخدام الأداة REPL من svelte.dev.

المكون MoreActions

سنعالج الآن الزرين "تحديد الكل Check All" و"حذف المهام المكتملة Remove Completed"، لذا لننشئ مكونًا يكون مسؤولًا عن عرض الأزرار وإصدار الأحداث المقابلة.

أولًا، أنشئ ملفًا جديدًا بالاسم components/MoreActions.svelte.

ثانيًا، سنرسل الحدث checkAll عند النقر على الزر الأول للإشارة إلى أنه يجب تحديد أو إلغاء تحديد جميع المهام، كما سنرسل الحدث removeCompleted عند النقر على الزر الثاني للإشارة إلى أنه يجب حذف جميع المهام المكتملة، لذا ضَع المحتوى التالي في الملف MoreActions.svelte:

<script>
  import { createEventDispatcher } from 'svelte'
  const dispatch = createEventDispatcher()

  let completed = true

  const checkAll = () => {
    dispatch('checkAll', completed)
    completed = !completed
  }

  const removeCompleted = () => dispatch('removeCompleted')

</script>

<div class="btn-group">
  <button type="button" class="btn btn__primary" on:click={checkAll}>{completed ? 'Check' : 'Uncheck'} all</button>
  <button type="button" class="btn btn__primary" on:click={removeCompleted}>Remove completed</button>
</div>

ضمّنا المتغير completed للتبديل بين تحديد جميع المهام وإلغاء تحديدها.

ثالثًا، سنستورد المكوِّن MoreActions مرةً أخرى في Todos.svelte وسننشئ دالتين للتعامل مع الأحداث الصادرة من المكوِّن MoreActions، لذا أضف تعليمة الاستيراد التالية بعد تعليمات الاستيراد الموجودة مسبقًا:

import MoreActions from './MoreActions.svelte'

رابعًا، أضف بعد ذلك الدوال الموضَّحة في نهاية القسم <script>:

const checkAllTodos = (completed) => todos.forEach((t) => t.completed = completed)

const removeCompletedTodos = () => todos = todos.filter((t) => !t.completed)

خامسًا، انتقل الآن إلى الجزء السفلي من شيفرة HTML الخاصة بـ Todos.svelte واستبدل العنصر <div> الذي له الصنف btn-group والذي نسخناه إلى MoreActions.svelte باستدعاء المكوِّن MoreActions كما يلي:

<!-- MoreActions -->
<MoreActions
  on:checkAll={e => checkAllTodos(e.detail)}
  on:removeCompleted={removeCompletedTodos}
/>

سادسًا، لنعد إلى التطبيق ونجربه، إذ ستجد أنّ زر "حذف المهام المكتملة Remove Completed" يعمل بصورة جيدة، ولكن يفشل الزر "تحديد الكل Check All" أو "إلغاء تحديد الكل Uncheck All".

اكتشاف التفاعل: تحديث الكائنات والمصفوفات

يمكننا تسجيل المصفوفة todos من الدالة checkAllTodos()‎ إلى الطرفية لمعرفة ما يحدث.

أولًا، عدّل الدالة checkAllTodos()‎ إلى ما يلي:

const checkAllTodos = (completed) => {
  todos.forEach((t) => t.completed = completed);
  console.log('todos', todos);
}

ثانيًا، ارجع إلى متصفحك وافتح طرفية أدوات التطوير DevTools وانقر على زر تحديد الكل أو إلغاء تحديد الكل عدة مرات.

ستلاحظ أنّ المصفوفة تُحدَّث بنجاح في كل مرة تضغط فيها على الزر، إذ تُبدَّل الخاصيات completed الخاصة بالكائنات todo بين القيمتين true و false، ولكن إطار Svelte ليس على علم بذلك، وهذا يعني أنه لن تكون تعليمة التفاعل مثل التعليمة ‎$: console.log('todos', todos)‎ مفيدةً جدًا في هذه الحالة، لذلك يجب فهم كيفية عمل التفاعل في إطار Svelte عند تحديث المصفوفات والكائنات.

تستخدِم العديد من أطر عمل الويب تقنية نموذج DOM الافتراضي لتحديث الصفحة، إذ يُعَدّ DOM الافتراضي نسخةً في الذاكرة لمحتويات صفحة الويب، كما يحدّث إطار العمل هذا التمثيل الافتراضي الذي تجري مزامنته بعد ذلك مع نموذج DOM الحقيقي، وهذا أسرع بكثير من التحديث المباشر لنموذج DOM ويسمح لإطار العمل بتطبيق العديد من تقنيات التحسين، إذ تعيد هذه الأطر تشغيل كل شيفرة جافاسكربت افتراضيًا في كل تغيير لنموذج DOM الافتراضي، وتطبّق توابعًا مختلفةً لتخزين العمليات الحسابية باهظة الثمن مؤقتًا ولتحسين التنفيذ.

لا يستخدِم إطار Svelte تمثيل نموذج DOM الافتراضي، وإنما يحلّل الشيفرة وينشئ شجرةً اعتماديةً، ثم ينشئ شيفرة جافاسكربت المطلوبة لتحديث أجزاء نموذج DOM التي تحتاج إلى تحديث فقط، إذ تنشئ هذه التقنية شيفرة جافاسكربت مثالية بأقل قدر من عمليات المعالجة إلى حد ما ولكن لذلك لا يخلو من بعض القيود.

يتعذر على إطار Svelte في بعض الأحيان اكتشاف التغييرات التي تطرأ على المتغيرات المُراقَبة، وتذكَّر أنه يمكنك إخبار إطار Svelte بتغيّر متغير ما من خلال إسناد قيمة جديدة إليه، كما يجب أن يظهر اسم المتغير المُحدَّث على الجانب الأيسر من هذا الإسناد كما يلي على سبيل المثال:

const foo = obj.foo
foo.bar = 'baz'

لن يحدِّث إطار عمل Svelte مراجع الكائن obj.foo.bar إلّا إذا تتبّعتها باستخدام الإسناد obj = obj، إذ لا يمكن لإطار عمل Svelte تتبّع مراجع الكائنات، لذلك يجب إخباره صراحةً أنّ الكائن obj تغير باستخدام الإسناد.

ملاحظة: إذا كان foo متغيرًا من المستوى الأعلى، فيمكنك بسهولة إخبار إطار Svelte بتحديث الكائن obj عندما يتغير المتغير foo باستخدام تعليمة التفاعل التالية: ‎$: foo, obj = obj، وبالتالي يُعرَّف foo على أنه اعتمادية، وكلما تغير، سيعمل إطار عمل Svelte على تشغيل عملية الإسناد obj = obj.

إذا شغلت ما يلي في الدالة checkAllTodos()‎:

todos.forEach((t) => t.completed = completed);

لن يلحظ إطار Svelte تغيّر المصفوفة todos لأنه لا يعرف أننا نعدّلها عند تحديث المتغير t ضمن التابع forEach()‎، ويُعَدّ ذلك منطقيًا، إذ سيعرف إطار Svelte عمل التابع forEach()‎ الداخلي إذا حدث عكس ذلك، لذا سيُطبَّق الأمر نفسه بالنسبة لأيّ تابع مرتبط بكائن أو مصفوفة، لكن هناك تقنيات مختلفة يمكننا تطبيقها لحل هذه المشكلة، وتتضمن جميعها إسناد قيمة جديدة للمتغير المُراقَب.

يمكننا إخبار إطار عمل Svelte بتحديث المتغير باستخدام إسناد ذاتي كما يلي:

const checkAllTodos = (completed) => {
  todos.forEach((t) => t.completed = completed);
  todos = todos;
}

تحل هذه الطريقة المشكلة، إذ سيرفع إطار Svelte رايةً تعبِّر عن تغيير المصفوفة todos ويزيل الإسناد الذاتي الذي يراه زائدًا، كما يمكن أن تبدو هذه الطريقة غريبةً، ولكنها تُعَدّ جيدةً ومختصَرةً.

يمكننا الوصول أيضًا إلى المصفوفة todos باستخدام الفهرس كما يلي:

const checkAllTodos = (completed) => {
  todos.forEach((t, i) => todos[i].completed = completed);
}

تعمل الإسنادات إلى خاصيات المصفوفات والكائنات مثل obj.foo += 1 أو array[i] = x بالطريقة نفسها للإسنادات إلى القيم نفسها، فإذا حلّل إطار عمل Svelte هذه الشيفرة، فيمكنه اكتشاف أنّ المصفوفة todos تُعدَّل.

يوجد حل آخر هو إسناد مصفوفة جديدة إلى المصفوفة todos، إذ تحتوي هذه المصفوفة الجديدة على نسخة من جميع المهام مع تحديث الخاصية completed وفقًا لذلك كما يلي:

const checkAllTodos = (completed) => {
  todos = todos.map((t) => ({ ...t, completed }));
}

نستخدِم في هذه الحالة التابع map()‎ الذي يعيد مصفوفةً جديدةً مع نتائج تنفيذ الدالة المتوفرة لكل عنصر، إذ تعيد الدالة نسخةً من كل مهمة باستخدام صيغة الانتشار Spread Syntax وتعيد كتابة خاصية القيمة completed وفقًا لذلك، وتتمثل فائدة هذا الحل في إعادة مصفوفة جديدة مع كائنات جديدة وتجنب تغيير المصفوفة todos الأصلية.

ملاحظة: يتيح إطار Svelte تحديد خيارات مختلفة تؤثر على كيفية عمل المصرِّف Compiler، إذ يخبر الخيار <svelte:options immutable={true}/‎> المصرِّف بأنك تتعهد بعدم تغيير أيّ كائنات، مما يتيح له بأن يكون أقل تحفظًا بشأن التحقق من تغيير القيم وإنشاء شيفرة أبسط وأكثر فعالية.

تتضمن كل هذه الحلول إسنادًا يكون فيه المتغير المحدَّث في الجانب الأيسر من المساواة، وستسمح جميعها لإطار Svelte بملاحظة تعديل المصفوفة todos، لذا اختر أحد هذه الحلول وحدّث الدالة checkAllTodos()‎ كما هو مطلوب، ويجب الآن أن تكون قادرًا على تحديد جميع مهامك وإلغاء تحديدها دفعةً واحدةً.

الانتهاء من المكون MoreActions

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

أولًا، عدّل المكوِّن MoreActions.svelte كما يلي:

<script>
  import { createEventDispatcher } from 'svelte'
  const dispatch = createEventDispatcher()

  export let todos

  let completed = true

  const checkAll = () => {
    dispatch('checkAll', completed)
    completed = !completed
  }

  const removeCompleted = () => dispatch('removeCompleted')

  $: completedTodos = todos.filter(t => t.completed).length
</script>

<div class="btn-group">
  <button type="button" class="btn btn__primary"
    disabled={todos.length === 0} on:click={checkAll}>{completed ? 'Check' : 'Uncheck'} all</button>
  <button type="button" class="btn btn__primary"
    disabled={completedTodos === 0} on:click={removeCompleted}>Remove completed</button>
</div>

صرّحنا عن متغير التفاعل completedTodos لتفعيل أو تعطيل زر "إزالة المهام المكتملة Remove Completed".

لا تنسى تمرير الخاصية إلى المكوِّن MoreActions من المكوِّن Todos.svelte حيث يُستدعَى المكوِّن كما يلي:

<MoreActions {todos}
    on:checkAll={(e) => checkAllTodos(e.detail)}
    on:removeCompleted={removeCompletedTodos}
  />

التعامل مع نموذج DOM: التركيز على التفاصيل

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

استكشاف مشاكل سهولة الوصول لمستخدمي لوحة المفاتيح في تطبيقنا

سيكتشف مستخدِمو لوحة المفاتيح حاليًا أنّ تدفق التركيز في تطبيقنا لا يمكن التنبؤ به أو غير مترابط، فإذا نقرت على حقل الإدخال في الجزء العلوي من تطبيقنا، فسترى حدًّا سميكًا ومتقطعًا حول هذا الحقل، إذ يُعَد هذا الحدّ المؤشر المرئي على أنّ المتصفح يركِّز حاليًا على هذا العنصر.

إذا كنت من مستخدمِي الفأرة، فيمكن أن تتخطى هذه الإشارة المرئية، ولكن إذا أردت العمل باستخدام لوحة المفاتيح فقط، فمعرفة عنصر التحكم المُركَّز عليه أمرٌ بالغ الأهمية، إذ يخبرنا هذا التركيز أيّ عنصر تحكم سيتلقى ضغطات المفاتيح، فإذا ضغطت على مفتاح Tab بصورة متكررة، فسترى مؤشر التركيز المتقطع يتنقل بين جميع العناصر القابلة للتركيز على الصفحة، وإذا نقلت التركيز إلى زر "التعديل Edit" وضغطتَ على مفتاح Enter، فسيختفي التركيز فجأة دون إمكانية تحديد عنصر التحكم الذي سيتلقى ضغطات المفاتيح. إذا ضغطت على مفتاح Escape أو Enter، فلن يحدث شيء؛ أما إذا نقرت على زر "الإلغاء Cancel" أو "الحفظ Save"، فسيختفي التركيز مرةً أخرى، كما سيكون هذا السلوك محيرًا بالنسبة لمستخدِم يعمل باستخدام لوحة المفاتيح.

كما نود إضافة بعض ميزات إمكانية الاستخدام مثل تعطيل زر "الحفظ Save" عندما تكون الحقول المطلوبة فارغةً، أو التركيز على بعض عناصر HTML أو التحديد التلقائي للمحتويات عند التركيز على حقل إدخال النص، كما يجب الوصول برمجيًا إلى عقد نموذج DOM لتشغيل دوال مثل الدالتين focus()‎ و select()‎ بهدف تطبيق جميع هذه الميزات، ويجب استخدام التابعين addEventListener()‎ و removeEventListener()‎ لتشغيل مهام محددة عندما يتلقى عنصر التحكم التركيز.

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

إنشاء المكون NewTodo

أنشئ ملف مكوِّن جديد وعدّل الشيفرة لإصدار الحدث addTodo من خلال تمرير اسم المهمة الجديدة مع التفاصيل الإضافية كما يلي:

أولًا، أنشئ ملفًا جديدًا بالاسم components/NewTodo.svelte.

ضع بعد ذلك المحتويات التالية ضمن هذا الملف:

<script>
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();

  let name = '';

  const addTodo = () => {
    dispatch('addTodo', name);
    name = '';
  }

  const onCancel = () => name = '';

</script>

<form on:submit|preventDefault={addTodo} on:keydown={(e) => e.key === 'Escape' && onCancel()}>
  <h2 class="label-wrapper">
    <label for="todo-0" class="label__lg">What needs to be done?</label>
  </h2>
  <input bind:value={name} type="text" id="todo-0" autoComplete="off" class="input input__lg" />
  <button type="submit" disabled={!name} class="btn btn__primary btn__lg">Add</button>
</form>

ربطنا هنا العنصر <input> بالمتغير name باستخدام bind:value={name}‎ وعطّلنا زر "الإضافة Add" عندما يكون حقل الإدخال فارغًا -أي لا يحتوي على محتوى نصي- باستخدام disabled={!name}‎، كما عالجنا استخدام مفتاح Escape باستخدام on:keydown={(e) => e.key === 'Escape' && onCancel()}‎، إذ نشغّل التابع onCancel()‎ الذي يمسح المتغير name في كل مرة نضغط فيها على مفتاح Escape.

يجب الآن استيراد import المكوِّن NewTodo واستخدامه ضمن المكوِّن Todos وتحديث الدالة addTodo()‎ للحصول على اسم المهمة الجديد، لذا أضف تعليمة الاستيراد التالية بعد تعليمات الاستيراد الأخرى الموجودة ضمن Todos.svelte:

import NewTodo from './NewTodo.svelte'

عدّل الدالة addTodo()‎ بعد ذلك كما يلي:

function addTodo(name) {
  todos = [...todos, { id: newTodoId, name, completed: false }]
}

تتلقى الدالة addTodo()‎ الآن اسم المهمة الجديدة مباشرةً، لذلك لم نَعُد بحاجة المتغير newTodoName لإعطائه قيمة، إذ سيهتم المكوِّن NewTodo بذلك.

ملاحظة: تُعَدّ الصيغة { name } اختصارًا للصيغة { name: name }، إذ يأتي هذا الاختصار من لغة جافاسكربت وليس له علاقة بإطار Svelte مع توفير بعض الإلهام للاختصارات الخاصة بإطار Svelte.

أخيرًا، استبدل شيفرة HTML الخاصة بنموذج NewTodo باستدعاء المكوِّن NewTodo كما يلي:

<!-- NewTodo -->
<NewTodo on:addTodo={(e) => addTodo(e.detail)} />

التعامل مع عقد نموذج DOM باستخدام الموجه bind:this={dom_node}‎

نريد الآن أن يعود التركيز إلى العنصر <input> الخاص بالمكون NewTodo في كل مرة يُضغَط فيها على زر "الإضافة Add"، لذا سنحتاج مرجعًا إلى عقدة نموذج DOM الخاصة بحقل الإدخال، إذ يوفر إطار عمل Svelte طريقةً لذلك باستخدام الموجِّه bind:this={dom_node}‎، كما يسند إطار Svelte مرجع عقدة DOM إلى متغير محدد بمجرد تثبيت المكوِّن وإنشاء عقدة DOM.

لننشئ المتغير nameEl ونربطه بحقل الإدخال باستخدام bind:this={nameEl}‎، ثم سنستدعي التابع nameEl.focus()‎ ضمن الدالة addTodo()‎ لإعادة التركيز إلى العنصر <input> مرةً أخرى بعد إضافة المهام الجديدة، وسنطبّق الشيء نفسه عندما يضغط المستخدِم على مفتاح Escape باستخدام الدالة onCancel()‎.

عدّل محتويات المكوِّن NewTodo.svelte كما يلي:

<script>
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();

  let name = '';
  let nameEl; // ‫مرجع إلى عقدة حقل الإدخال name في نموذج DOM

  const addTodo = () => {
    dispatch('addTodo', name);
    name = '';
    nameEl.focus(); // ‫ركّز على حقل الإدخال name
  }

  const onCancel = () => {
    name = '';
    nameEl.focus(); // ‫ركّز على حقل الإدخال name
  }
</script>

<form on:submit|preventDefault={addTodo} on:keydown={(e) => e.key === 'Escape' && onCancel()}>
  <h2 class="label-wrapper">
    <label for="todo-0" class="label__lg">What needs to be done?</label>
  </h2>
  <input bind:value={name} bind:this={nameEl} type="text" id="todo-0" autoComplete="off" class="input input__lg" />
  <button type="submit" disabled={!name} class="btn btn__primary btn__lg">Add</button>
</form>

جرِّب التطبيق واكتب اسم مهمة جديدة في حقل الإدخال <input> واضغط على المفتاح tab للتركيز على زر "الإضافة Add"، ثم اضغط على مفتاح Enter أو Escape لترى كيف يستعيد حقل الإدخال التركيز.

التركيز التلقائي على حقل الإدخال

الميزة التالية التي سنضيفها إلى المكوِّن NewTodo هي الخاصية autofocus التي ستسمح بتحديد أننا نريد التركيز على حقل الإدخال <input> في صفحة التحميل.

محاولتنا الأولى هي كما يلي: لنحاول إضافة الخاصية autofocus واستدعاء التابع nameEl.focus()‎ في كتلة القسم <script>، لذا عدِّل الجزء الأول من القسم <script> الخاص بالمكوِّن NewTodo.svelte (الأسطر الأربعة الأولى) لتبدو كما يلي:

<script>
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();

  export let autofocus = false;

  let name = '';
  let nameEl; // مرجع إلى عقدة حقل الإدخال‫ name في نموذج DOM

  if (autofocus) nameEl.focus();

عُد الآن إلى المكوِّن Todos ومرّر الخاصية autofocus إلى استدعاء المكوِّن <NewTodo> كما يلي:

<!-- NewTodo -->
<NewTodo autofocus on:addTodo={(e) => addTodo(e.detail)} />

إذا جربت تطبيقك، فسترى أنّ الصفحة فارغة حاليًا، وسترى في طرفية أدوات تطوير الويب خطأً بالشكل: TypeError: nameEl is undefined.

دورة حياة المكون والدالة onMount()‎

يشغّل إطار Svelte شيفرة التهيئة -أي قسم <script> الخاص بالمكون- عند إنشاء نسخة من هذا المكون، ولكن تكون جميع العقد التي يتألف منها المكوِّن غير مرتبطة بنموذج DOM في تلك اللحظة، وهي في الحقيقة غير موجودة أصلًا، كما يمكن أن تتساءل عن كيفية معرفة وقت إنشاء المكوِّن فعليًا وتثبيته على نموذج DOM، والإجابة هي أنه لكل مكوِّن دورة حياة تبدأ عند إنشائه وتنتهي عند تدميره، وهناك عدد من الدوال التي تسمح بتشغيل الشيفرة في اللحظات المهمة خلال دورة الحياة هذه.

الدالة التي ستستخدِمها بكثرة هي الدالة onMount()‎ والتي تتيح تشغيل دالة رد نداء Callback بمجرد تثبيت المكوِّن على نموذج DOM، لذا لنجربها ونرى ما سيحدث للمتغير nameEl.

أضِف أولًا السطر التالي في بداية القسم <script> الخاص بالمكوِّن NewTodo.svelte:

import { onMount } from 'svelte';

وأضِف الأسطر التالية في نهايته:

console.log('initializing:', nameEl);
onMount( () => {
  console.log('mounted:', nameEl);
})

احذف الآن السطر if (autofocus) nameEl.focus()‎ لتجنب الخطأ الذي رأيناه سابقًا.

سيعمل التطبيق الآن مرةً أخرى، وسترى ما يلي في الطرفية:

initializing: undefined
mounted: <input id="todo-0" class="input input__lg" type="text" autocomplete="off">

يكون المتغير nameEl غير مُعرَّف أثناء تهيئة المكوِّن، وهو أمر منطقي لأن عقدة حقل الإدخال <input> غير موجودة حتى الآن، لذا أسند إطار عمل Svelte مرجع العقدة <input> في نموذج DOM إلى المتغير nameEl بفضل الموجّه bind:this={nameEl}‎ بعد تثبيت المكوِّن.

يمكنك تشغيل وظيفة التركيز التلقائي من خلال استبدال كتلة onMount()‎ ضمن console.log()‎ السابقة بما يلي:

onMount(() => autofocus && nameEl.focus());
 // ‫سنشغّل التابع nameEl.focus()‎ إذا كانت قيمة autofocus هي true

انتقل إلى تطبيقك مرةً أخرى وسترى الآن تركيز حقل الإدخال <input> على صفحة التحميل.

انتظار تحديث نموذج DOM باستخدام الدالة tick()‎

سنهتم الآن بتفاصيل إدارة تركيز المكون Todo، إذ نريد أولًا أن يستلم تعديل حقل الإدخال <input> الخاص بالمكوِّن Todo التركيز عند الدخول في وضع التعديل من خلال الضغط على زر "التعديل Edit"، كما سننشئ المتغير nameEl ضمن المكوِّن Todo.svelte وسنستدعي التابع nameEl.focus()‎ بعد ضبط المتغير editing على القيمة true.

أولًا، افتح الملف components/Todo.svelte وأضِف التصريح عن المتغير nameEl التالي بعد التصريح عن المتغيرين editing و name مباشرةً:

let nameEl;                              //  مرجع إلى عقدة حقل الإدخال‫ name في نموذج DOM

ثانيًا، عدّل الدالة onEdit()‎ كما يلي:

function onEdit() {
  editing = true;                        // الدخول في وضع التعديل
  nameEl.focus();                        // ‫ضبط التركيز على حقل الإدخال name
}

أخيرًا، اربط المتغير nameEl بحقل الإدخال <input> من خلال تعديله كما يلي:

<input
  bind:value={name}
  bind:this={nameEl}
  type="text"
  id="todo-{todo.id}"
  autocomplete="off"
  class="todo-text" />

ولكن ستحصل على خطأ بالشكل: "TypeError: nameEl is undefined" في الطرفية عند الضغط على زر تعديل المهمة.

لا يحدّث إطار عمل Svelte نموذج DOM مباشرةً عند تحديث حالة المكوِّن، وإنما ينتظر حتى المهمة السريعة microtask التالية لمعرفة ما إذا كانت هناك أيّ تغييرات أخرى يجب تطبيقها بما في ذلك التغييرات في المكونات الأخرى، مما يؤدي إلى تجنب العمل غير الضروري ويسمح للمتصفح بتجميع الأشياء بطريقة أكثر فعالية.

لا يكون تعديل حقل الإدخال <input> مرئيًا في هذه الحالة لأنه غير موجود في نموذج DOM عندما يكون للمتغير editing القيمة false، لذا اضبط editing = true في الدالة onEdit()‎ وحاول بعد ذلك مباشرةً الوصول إلى المتغير nameEl ونفّذ التابع nameEl.focus()‎، ولكن المشكلة هنا هي أنّ إطار عمل Svelte لم يحدّث نموذج DOM بعد.

تتمثل إحدى طرق حل هذه المشكلة في استخدام التابع setTimeout()‎ لتأخير استدعاء التابع nameEl.focus()‎ حتى دورة الأحداث التالية وإعطاء إطار عمل Svelte الفرصة لتحديث نموذج DOM كما يلي:

function onEdit() {
  editing = true;                        // الدخول في وضع التعديل
  setTimeout(() => nameEl.focus(), 0);   // استدعاء غير متزامن لضبط التركيز على حقل الإدخال‫ name
}

الحل السابق جيد، ولكنه غير مرتب إلى حد ما، إذ يوفر إطار Svelte طريقةً أفضل للتعامل مع هذه الحالات، حيث تعيد الدالة tick()‎ وعدًا Promise يُحَل بمجرد تطبيق أيّ تغييرات على حالة مُعلَّقة في نموذج DOM، أو مباشرةً إذا لم تكن هناك تغييرات على حالة مُعلَّقة.

استورد أولًا tick في بداية القسم <script> مع تعليمات الاستيراد الموجودة مسبقًا كما يلي:

import { tick } from 'svelte'

استدعِ بعد ذلك الدالة tick()‎ مع المعامِل await من دالة غير متزامنة، وعدّل الدالة onEdit()‎ كما يلي:

async function onEdit() {
  editing = true;                        // الدخول في وضع التعديل
  await tick();
  nameEl.focus();
}

إذا جربت التطبيق الآن، فسترى أنّ كل شيء يعمل كما هو متوقع.

إضافة وظائف إلى عناصر HTML باستخدام الموجه use:action

نريد بعد ذلك أن يحدّد حقل الإدخال <input> كل النص عند التركيز عليه، كما نريد تطوير ذلك بطريقة يمكن إعادة استخدامه بسهولة على أيّ عنصر <input> في HTML وتطبيقه بطريقة تصريحية، وسنستخدِم هذا المتطلب بوصفه سببًا لإظهار ميزة قوية جدًا يوفرها إطار Svelte لإضافة وظائف لعناصر HTML العادية، وهذه الميزة هي الإجراءات actions.

يمكنك تحديد نص عقدة حقل إدخال في نموذج DOM من خلال استدعاء التابع select()‎، إذ يجب استخدام مستمع أحداث لاستدعاء هذه الدالة كلما انتقل التركيز إلى هذه العقدة كما يلي:

node.addEventListener('focus', event => node.select())

كما يجب استدعاء الدالة removeEventListener()‎ عند تدمير العقدة لتجنب تسرّب الذاكرة Memory Leak.

ملاحظة: كل ما سبق هو مجرد وظيفة قياسية من واجهة WebAPI دون وجود شيء خاص بإطار عمل Svelte.

يمكن تحقيق كل ذلك في المكوِّن Todo كلما أضفنا أو أزلنا عنصر <input> من نموذج DOM، ولكن يجب أن نكون حريصين جدًا على إضافة مستمع الأحداث بعد إضافة العقدة إلى نموذج DOM وإزالة المستمع قبل إزالة العقدة من نموذج DOM. كما أنّ هذا الحل لن يكون قابلًا لإعادة الاستخدام بصورة كبيرة، وهنا يأتي دور إجراءات إطار Svelte التي تسمح بتشغيل دالة كلما أُضيف عنصر إلى نموذج DOM وبعد إزالته من نموذج DOM.

سنعرّف دالة بالاسم selectOnFocus()‎ تأخذ عقدة على أساس معامل لها، وستضيف هذه الدالة مستمع أحداث إلى تلك العقدة بحيث يُحدَّد النص كلما انتقل التركيز إليها، ثم ستعيد كائنًا مع الخاصية destroy التي سينفذها إطار Svelte بعد إزالة العقدة من نموذج DOM، وسنزيل هنا المستمع للتأكّد من أننا لا نترك أيّ تسرّب للذاكرة خلفنا.

أولًا، لننشئ الدالة selectOnFocus()‎، لذا أضف ما يلي إلى أسفل القسم <script> الخاص بالمكوِّن Todo.svelte:

function selectOnFocus(node) {
  if (node && typeof node.select === 'function' ) {           // ‫تأكّد من أن العقدة مُعرَّفة ولديها التابع select()‎
    const onFocus = event => node.select();                        // معالج الحدث
    node.addEventListener('focus', onFocus);                      // ‫استدعِ التابع onFocus()‎ عندما ينتقل التركيز إلى العقدة
    return {
      destroy: () => node.removeEventListener('focus', onFocus)   // ‫سيُنفَّذ هذا السطر عند إزالة العقدة من نموذج DOM
    }
  }
}

يجب الآن إعلام حقل الإدخال <input> بأن يستخدِم هذه الدالة من خلال الموجّه use:action كما يلي:

<input use:selectOnFocus />

نطلب باستخدام هذا الموجّه من إطار Svelte تشغيل هذه الدالة وتمرير عقدة نموذج DOM الخاصة بحقل الإدخال <input> بوصفها معاملًا لها بمجرد تثبيت المكوِّن على نموذج DOM، وسيكون مسؤولًا عن تنفيذ الدالة destroy عند إزالة المكوِّن من نموذج DOM، وبالتالي يهتم Svelte بدورة حياة المكوِّن باستخدام الموجّه use، وسيكون العنصر <input> في حالتنا كما يلي:

عدّل أول زوج تسمية أو عنوان/حقل إدخال label/input للمكوِّن ضمن قالب التعديل على النحو التالي:

<label for="todo-{todo.id}" class="todo-label">New name for '{todo.name}'</label>
<input
  bind:value={name}
  bind:this={nameEl}
  use:selectOnFocus
  type="text"
  id="todo-{todo.id}"
  autocomplete="off"
  class="todo-text" />

انتقل إلى تطبيقك واضغط على زر تعديل المهام ثم اضغط على المفتاح Tab لإبعاد التركيز عن العنصر <input>، ثم انقر عليه وسترى تحديد نص حقل الإدخال بالكامل.

جعل الإجراء قابلا لإعادة الاستخدام

لنجعل الآن هذه الدالة قابلة لإعادة الاستخدام بين المكونات، إذ تُعَدّ الدالة selectOnFocus()‎ مجرد دالة لا تعتمد على المكوِّن Todo.svelte، لذا يمكننا وضعها في ملف واستخدامها من هناك.

أولًا، أنشئ ملفًا جديدًا بالاسم actions.js ضمن المجلد src.

ثانيًا، ضع فيه المحتوى التالي:

export function selectOnFocus(node) {
  if (node && typeof node.select === 'function' ) {               //  ‫تأكّد من أن العقدة مُعرَّفة ولديها التابع select()‎
    const onFocus = event => node.select();                        // معالج الحدث
    node.addEventListener('focus', onFocus);                       // يُستدعى عند التركيز على القعدة
    return {
      destroy: () => node.removeEventListener('focus', onFocus)   //  ‫سيُنفَّذ هذا السطر عند إزالة العقدة من نموذج DOM
    }
  }
}

استورده من داخل المكوِّن Todo.svelte من خلال إضافة تعليمة الاستيراد التالية:

import { selectOnFocus } from '../actions.js'

احذف تعريف الدالة selectOnFocus()‎ من المكوِّن Todo.svelte، لأننا لم نعُد بحاجة إليها هناك.

إعادة استخدام الإجراء

لنستخدم الإجراء في المكوِّن NewTodo.svelte لإثبات إمكانية إعادة استخدامه.

أولًا، استورد الدالة selectOnFocus()‎ من الملف actions.js في الملف NewTodo.svelte كما يلي:

import { selectOnFocus } from '../actions.js';

ثانيًا، أضف الموجّه use:selectOnFocus إلى العنصر <input> كما يلي:

<input
  bind:value={name}
  bind:this={nameEl}
  use:selectOnFocus
  type="text"
  id="todo-0"
  autocomplete="off"
  class="input input__lg" />

يمكننا إضافة وظائف لعناصر HTML العادية بطريقة قابلة لإعادة الاستخدام وتصريحية باستخدام بضعة أسطر من الشيفرة البرمجية، إذ يتطلب الأمر استيرادًا import وموجّهًا قصيرًا مثل الموجّه use:selectOnFocus الذي يوضِّح الغرض منه، ويمكننا تحقيق ذلك دون الحاجة إلى إنشاء عنصر مُغلِّف مخصَّص مثل TextInput أو MyInput أو ما شابه ذلك، كما يمكنك إضافة العديد من موجّهات use:action إلى عنصر ما.

كما أنه ليس علينا أن نعاني باستخدام الدوال onMount()‎ أو onDestroy()‎ أو tick()‎، إذ يهتم الموجّه use بدورة حياة المكوِّن.

تحسينات الإجراءات الأخرى

كان علينا في القسم السابق أثناء العمل مع مكونات Todo التعاملَ مع الدوال bind:this و tick()‎ و async للتركيز على حقل الإدخال <input> بمجرد إضافته إلى نموذج DOM.

يمكننا تطبيق ذلك باستخدام الإجراءات كما يلي:

const focusOnInit = (node) => node && typeof node.focus === 'function' && node.focus();

يجب بعد ذلك إضافة موجّه use:‎ آخر في شيفرة HTML كما يلي:

<input bind:value={name} use:selectOnFocus use:focusOnInit />

يمكن الآن أن تكون الدالة onEdit()‎ أبسط كما يلي:

function onEdit() {
  editing = true;                        // الدخول في وضع التعديل
}

لنعُد إلى المكوِّن Todo.svelte ونركّز على زر "التعديل Edit" بعد أن يضغط المستخدِم على زر "الحفظ Save" أو "الإلغاء Cancel".

يمكننا محاولة إعادة استخدام الإجراء focusOnInit مرة أخرى من خلال إضافة الموجّه use:focusOnInit إلى زر "التعديل Edit"، لكننا سندخِل بذلك زلة برمجية، إذ سينتقل التركيز عند إضافة مهمة جديدة إلى زر "التعديل Edit" الخاص بالمهمة التي أُضيفت مؤخرًا بسبب تشغيل الإجراء focusOnInit عند إنشاء المكوِّن.

لا نريد ذلك، وإنما نريد أن يستلم زر "التعديل Edit" التركيز فقط عندما يضغط المستخدِم على زر "الحفظ Save" أو "الإلغاء Cancel".

لذا ارجع إلى الملف Todo.svelte، إذ سننشئ أولًا رايةً بالاسم editButtonPressed ونهيّئها بالقيمة false، لذا أضف ما يلي بعد تعريفات المتغيرات الأخرى:

let editButtonPressed = false;           // تتبّع إذا ضُغِط على زر التعديل للتركيز عليه بعد الإلغاء أو الحفظ

سنعدّل بعد ذلك وظيفة زر "التعديل Edit" لحفظ هذه الراية وإنشاء إجرائها الخاص، لذا عدّل الدالة onEdit()‎ كما يلي:

function onEdit() {
  editButtonPressed = true;              // سيؤدي ضغط المستخدم على زر التعديل إلى عودة التركيز إليه
  editing = true;                        // الدخول في وضع التعديل
}

أضِف بعد ذلك تعريف الدالة focusEditButton()‎ التالي:

const focusEditButton = (node) => editButtonPressed && node.focus();

أخيرًا، استخدم الموجّه use:focusEditButton مع زر "التعديل Edit" كما يلي:

<button type="button" class="btn" on:click={onEdit} use:focusEditButton>
  Edit<span class="visually-hidden"> {todo.name}</span>
</button>

جرّب تطبيقك مرةً أخرى، إذ يُنفَّذ الإجراء focusEditButton في هذه المرحلة في كل مرة يُضاف فيها زر "التعديل Edit" إلى نموذج DOM، ولكنه سيعطي التركيز فقط للزر إذا كانت قيمة الراية editButtonPressed هي true.

ملاحظة: لم نتعمق كثيرًا في الإجراءات هنا، إذ يمكن أن تحتوي الإجراءات على معامِلات تفاعلية، ويتيح إطار Svelte اكتشاف متى يتغير أيّ من هذه المعامِلات لنتمكن من إضافة وظائف تتكامل جيدًا مع نظام التفاعل في إطار Svelte، كما تُعَدّ الإجراءات مفيدةً للتكامل بسلاسة مع المكتبات الخارجية.

ربط المكونات: الوصول إلى توابع ومتغيرات المكون باستخدام الموجه bind:this={component}‎

توجد مشكلة أخرى وهي أنه يتلاشى التركيز عندما يضغط المستخدِم على زر "الحذف Delete"، إذ تتضمن الميزة الأخيرة التي سنشرحها في هذا المقال ضبط التركيز على عنوان الحالة بعد حذف مهمة.

اخترنا التركيز على عنوان الحالة بسبب حذف العنصر الذي جرى التركيز عليه، لذلك لا يوجد عنصر آخر واضح لتلقي التركيز، إذ يُعَدّ عنوان الحالة قريبًا من قائمة المهام، وهو طريقة مرئية لمعرفة إزالة المهمة بالإضافة إلى توضيح ما حدث لمستخدِمي قارئ الشاشة.

أولًا، أنشئ ملفًا جديدًا بالاسم components/TodosStatus.svelte.

ثانيًا، أضِف إليه المحتويات التالية:

<script>
  export let todos;

  $: totalTodos = todos.length;
  $: completedTodos = todos.filter((todo) => todo.completed).length;
</script>

<h2 id="list-heading">
  {completedTodos} out of {totalTodos} items completed
</h2>

ثالثًا، استورد هذا الملف في بداية المكوِّن Todos.svelte من خلال إضافة تعليمة الاستيراد import التالية بعد تعليمات الاستيراد الأخرى:

import TodosStatus from './TodosStatus.svelte';

رابعًا، استبدل عنوان الحالة <h2> ضمن الملف Todos.svelte باستدعاء المكوِّن TodosStatus من خلال تمرير todos إليه بوصفها خاصيةً كما يلي:

<TodosStatus {todos} />

خامسًا، أزِل المتغيرين totalTodos و completedTodos من المكوِّن Todos.svelte، إذ ما عليك سوى إزالة السطرين ‎$: totalTodos = ...‎ و‎$: completedTodos = ...‎ وإزالة المرجع إلى المتغير totalTodos عندما نحسب newTodoId واستخدم todos.length بدلًا من ذلك، أي استبدل الكتلة التي تبدأ بالسطر let newTodoId بما يلي:

$: newTodoId = todos.length ? Math.max(...todos.map(t => t.id)) + 1 : 1;

يعمل كل شيء كما هو متوقع، واستخرجنا للتو آخر جزء من شيفرة HTML إلى مكوِّنه الخاص.

يجب الآن إيجاد طريقة للتركيز على تسمية الحالة <h2> بعد إزالة المهمة، إذ رأينا حتى الآن كيفية إرسال المعلومات إلى مكوِّن باستخدام الخاصيات Props، وكيف يمكن للمكوِّن التواصل مع المكوِّن الأب عن طريق إصدار أحداث أو استخدام ربط البيانات ثنائي الاتجاه، إذ يمكن للمكوِّن الابن الحصول على مرجع إلى العقدة <h2> باستخدام الموجّه bind:this={dom_node}‎ ويمكن للمكونات الخارجية الوصول إليه باستخدام ربط البيانات ثنائي الاتجاه، لكن سيؤدي ذلك إلى كسر تغليف المكوِّن، لذلك نحن بحاجة إلى المكوِّن TodosStatus للوصول إلى تابع يمكن للمكوِّن الابن استدعاؤه للتركيز علي، إذ تُعَدّ حاجة المكوِّن لإمكانية وصول المستخدِم لبعض السلوك أو المعلومات أمرًا شائعًا جدًا، لذا لنرى كيفية تحقيق ذلك في إطار عمل Svelte.

رأينا سابقًا أن إطار عمل Svelte يستخدِم التعليمة export let varname = ...‎ للتصريح عن الخاصيات، ولكن إذا صدّرتَ ثابتًا const أو صنفًا class أودالةً function بدلًا من استخدام let لوحدها، فستكون للقراءة فقط خارج المكوِّن، وتُعَدّ تعابير الدوال خاصيات صالحةً.

تُعَدّ التصريحات الثلاثة الأولى في المثال التالي خاصيات، والتصريحات الأخرى هي عبارة عن قيم مُصدَّرة:

<script>
  export let bar = "optional default initial value";       // خاصية
  export let baz = undefined;                              // خاصية
  export let format = n => n.toFixed(2);                   // خاصية

  // these are readonly
  export const thisIs = "readonly";                        // تصدير للقراءة فقط

  export function greet(name) {                           // تصدير للقراءة فقط
    alert(`hello ${name}!`);
  }

  export const greet = (name) => alert(`hello ${name}!`);  // تصدير للقراءة فقط
</script>

لننشئ تابعًا بالاسم focus()‎ يركّز على العنوان <h2>، لذا سنحتاج إلى المتغير headingEl للاحتفاظ بالمرجع إلى عقدة DOM، ويجب ربطه بالعنصر <h2> باستخدام الموجّه ‎‎bind:this={headingEl}‎‎‎، إذ سيشغّل تابع التركيز فقط headingEl.focus()‎.

أولًا، عدّل محتويات المكوِّن TodosStatus.svelte كما يلي:

<script>
  export let todos;

  $: totalTodos = todos.length;
  $: completedTodos = todos.filter((todo) => todo.completed).length;

  let headingEl;

  export function focus() {
    // shorter version: export const focus = () => headingEl.focus()
    headingEl.focus();
  }
</script>

<h2 id="list-heading" bind:this={headingEl} tabindex="-1">
  {completedTodos} out of {totalTodos} items completed
</h2>

لاحظ أننا أضفنا السمة tabindex إلى العنوان <h2> للسماح للعنصر بتلقي التركيز برمجيًا، إذ يعطينا استخدام الموجِّه bind:this={headingEl}‎ مرجعًا إلى عقدة DOM في المتغير headingEl كما رأينا سابقًا، كما نستخدم بعد ذلك التعليمة export function focus()‎ لإمكانية الوصول إلى دالة تركّز على العنوان <h2>، كما يمكنك ربط نسخ المكوِّن باستخدام الموجِّه bind:this={component}‎ مثل ربط عناصر DOM باستخدام الموجّه bind:this={dom_node}‎، لذا تحصل على مرجع لعقدة DOM عند استخدام الموجِّه bind:this مع عنصر HTML، وتحصل على مرجع إلى نسخة من هذا المكوِّن عندما تفعل ذلك مع مكوِّن Svelte.

ثانيًا، سننشئ أولًا المتغير todosStatus في Todos.svelte للربط بنسخة من المكوِّن Todos.svelte، لذا أضف السطر التالي بعد تعليمات الاستيراد الموجودة مسبقًا:

let todosStatus;                   // ‫مرجع إلى نسخة من المكون TodosStatus

ثالثًا، أضِف بعد ذلك الموجّه bind:this={todosStatus}‎ إلى الاستدعاء كما يلي:

<!-- TodosStatus -->
<TodosStatus bind:this={todosStatus} {todos} />

رابعًا، يمكننا الآن استدعاء التابع focus()‎ المُصدَّر من التابع removeTodo()‎ كما يلي:

function removeTodo(todo) {
  todos = todos.filter((t) => t.id !== todo.id);
  todosStatus.focus();             // ركّز على عنوان الحالة
}

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

ملاحظة: يمكن أن تتساءل عن سبب حاجتنا للتصريح عن متغير جديد لربط المكون بالرغم من أنه يمكننا فقط استدعاء التابع TodosStatus.focus()‎، إذ يمكن أن يكون لديك العديد من نسخ المكوِّن TodosStatus النشطة، لذلك تحتاج لطريقة للرجوع إلى كل نسخة معينة، وبالتالي يجب تحديد متغير لربط كل نسخة محددة به.

يمكنك الوصول إلى نسختك من مستودعنا على النحو التالي لمعرفة حالة الشيفرة كما يجب أن تكون في نهاية هذا المقال:

cd mdn-svelte-tutorial/06-stores

أو يمكنك تنزيل محتوى المجلد مباشرةً باستخدام الأمر التالي:

npx degit opensas/mdn-svelte-tutorial/06-stores

تذكَّر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير، فإذا أردت متابعتنا، فابدأ بكتابة الشيفرة باستخدام الأداة REPL من موقع svelte.dev.

الخلاصة

انتهينا في هذا المقال من إضافة جميع الوظائف المطلوبة إلى تطبيقنا، بالإضافة إلى اهتمامنا بعدد من مشاكل سهولة الوصول وسهولة الاستخدام، وانتهينا من تقسيم تطبيقنا إلى مكونات يمكن إدارتها مع إعطاء كل منها مسؤولية فريدة، كما رأينا بعض تقنيات إطار عمل Svelte المتقدمة مثل:

  • التعامل مع اكتشاف التفاعل عند تحديث العناصر والمصفوفات.
  • العمل مع عقد DOM باستخدام الموجّه bind:this={dom_node}‎ (ربط عناصر DOM).
  • استخدام الدالة onMount()‎ الخاصة بدورة حياة المكوِّن.
  • إجبار إطار عمل Svelte على حل تغييرات الحالة المُعلَّقة باستخدام الدالة tick()‎.
  • إضافة وظائف لعناصر HTML بطريقة تصريحية وقابلة لإعادة الاستخدام باستخدام الموجّه use:action.
  • الوصول إلى توابع المكونات باستخدام الموجّه bind:this={component}‎ (ربط المكونات).

سنرى في المقال التالي كيفية استخدام المخازن Stores للتواصل بين المكونات وإضافة الحركة إلى المكونات.

ترجمة -وبتصرُّف- للمقال Advanced Svelte: Reactivity, lifecycle, accessibility.

اقرأ أيضًا


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...