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

استخدام المخازن Stores في إطار عمل Svelte


Ola Abbas

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

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

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

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

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

cd mdn-svelte-tutorial/06-stores

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

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

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

التعامل مع حالة تطبيقنا

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

كما يمكن أن يصبح ترحيل البيانات بين المكونات صعبًا جدًا عندما يكون تطبيقك وتسلسل المكونات الهرمي معقّدَين، لذا يمكن أن يكون الانتقال إلى مخزن بيانات عام خيارًا جيدًا في هذه الحالة، فإذا استخدمت مسبقًا مكتبتَي Redux أو Vuex، فستكون على دراية بكيفية عمل هذا النوع من المخازن، إذ تقدّم مخازن Svelte ميزات مماثلة لإدارة الحالة.

يُعَدّ المخزن Store كائنًا يمتلك التابع subscribe()‎ الذي يسمح بإعلام الأطراف المهتمة كلما تغيرت قيمة هذا المخزن، والتابع الاختياري set()‎ الذي يسمح بضبط قيم جديدة للمخزن، إذ يُعرَف الحد الأدنى من واجهة برمجة التطبيقات باسم عَقد المخزن Store Contract.

يوفر إطار عمل Svelte دوالًا لإنشاء مخازن قابلة للقراءة والكتابة ومشتقة في وحدة svelte/store، كما يوفر طريقةً بسيطةً لدمج المخازن في نظام التفاعل باستخدام الصيغة التفاعلية ‎$store، فإذا أنشأتَ مخازنك الخاصة مع التقيّد بعَقد المخزن، فستحصل على هذه الصيغة المُختصَرة التفاعلية مجانًا.

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

سننشئ المكوِّن Alert لتوضيح كيفية العمل مع المخازن، ويمكن أن تُعرَف هذه الأنواع من عناصر واجهة المستخدِم باسم الإشعارات المنبثقة أو الرسائل المؤقتة toast أو الإشعارات الفقاعية Notification Bubbles.

سيعرض المكوِّنُ App المكوِّنَ Alert، ولكن يمكن لأيّ مكوِّن إرسال إشعارات إليه، حيث سيكون المكوِّن Alert مسؤولًا عن عرض الإشعار على الشاشة عند وصوله.

إنشاء مخزن

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

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

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

import { writable } from 'svelte/store'

export const alert = writable('Welcome to the to-do list app!')

ملاحظة: يمكن تعريف المخازن واستخدامها خارج مكونات Svelte بحيث يمكنك تنظيمها بأيّ طريقة تريدها.

استوردنا في الشيفرة السابقة الدالة writable()‎ من الوحدة svelte/store واستخدمناها لإنشاء مخزن جديد يسمى alert مع قيمة أولية هي Welcome to the to-do list app!‎ ثم صدّرنا هذا المخزن.

إنشاء المكون الفعلي

لننشئ المكوِّن Alert ونرى كيف يمكننا قراءة القيم من المخزن.

أولًا، أنشئ ملفًا جديدًا آخر بالاسم src/components/Alert.svelte.

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

<script>
  import { alert } from '../stores.js'
  import { onDestroy } from 'svelte'

  let alertContent = ''

  const unsubscribe = alert.subscribe(value => alertContent = value)

  onDestroy(unsubscribe)
</script>

{#if alertContent}
<div on:click={() => alertContent = ''}>
  <p>{ alertContent }</p>
</div>
{/if}

<style>
div {
  position: fixed;
  cursor: pointer;
  margin-right: 1.5rem;
  margin-left: 1.5rem;
  margin-top: 1rem;
  right: 0;
  display: flex;
  align-items: center;
  border-radius: 0.2rem;
  background-color: #565656;
  color: #fff;
  font-size: 0.875rem;
  font-weight: 700;
  padding: 0.5rem 1.4rem;
  font-size: 1.5rem;
  z-index: 100;
  opacity: 95%;
}
div p {
  color: #fff;
}
div svg {
  height: 1.6rem;
  fill: currentColor;
  width: 1.4rem;
  margin-right: 0.5rem;
}
</style>

لنتعرّف على تفاصيل الشيفرة البرمجية السابقة:

  • نستورد أولًا المخزن alert.
  • ثم نستورد دالة دورة الحياة onDestroy()‎ التي تتيح تنفيذ دالة رد نداء بعد إلغاء تثبيت المكوِّن.
  • ثم ننشئ متغيرًا محليًا بالاسم alertContent، وتذكّر أنه يمكننا الوصول إلى متغيرات المستوى الأعلى من شيفرة HTML، وكلما عُدِّلت، سيُحدَّث نموذج DOM وفقًا لذلك.
  • ثم نستدعي التابع alert.subscribe()‎ ونمرِّر له دالة رد نداء بوصفها معاملًا، وكلما تغيرت قيمة المخزن، ستُستدعَى دالة رد النداء مع القيمة الجديدة بوصفها معاملًا لها، كما نسند القيمة التي نتلقاها إلى متغير محلي في دالة رد النداء، مما يؤدي إلى تحديث نموذج DOM الخاص بالمكوِّن.
  • يعيد التابع subscribe()‎ دالة التنظيف التي تتولى تحرير الاشتراك، وبذلك نشترك عند تهيئة المكوِّن ونستخدِم الدالة onDestroy لإلغاء الاشتراك عندما يكون المكوِّن غير مثبَّت.
  • نستخدِم أخيرًا المتغير alertContent في شيفرة HTML، فإذا نقر المستخدِم على التنبيه، فسننظفه.
  • نضمّن في النهاية عددًا من سطور CSS لتنسيق المكوِّن Alert.

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

استخدام المكون

لنستخدِم الآن مكوننا الخاص.

أولًا، سنستورد المكوِّن في الملف App.svelte، لذا أضِف تعليمة الاستيراد التالية بعد تعليمات الاستيراد الأخرى الموجودة مسبقًا:

import Alert from './components/Alert.svelte'

ثم استدعِ المكوِّن Alert قبل استدعاء المكوِّن Todos مباشرةً كما يلي:

<Alert />
<Todos {todos} />

حمّل تطبيقك التجريبي الآن، وسترى الآن رسالة تنبيه Alert على الشاشة، إذ يمكنك النقر عليها لإبعادها.

رسالة تنبيه Alert على الشاشة في التطبيق التجريبي

جعل المخازن تفاعلية باستخدام الصيغة التفاعلية ‎$store

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

<script>
  import myStore from "./stores.js";
  import { onDestroy } from "svelte";

  let myStoreContent = "";

  const unsubscribe = myStore.subscribe((value) => (myStoreContent = value));

  onDestroy(unsubscribe);
</script>

{myStoreContent}

تُعَدّ الشيفرة البرمجية السابقة مكررةً بكثرة، وهذا كثير على إطار عمل Svelte، لأنه يمتلك مزيدًا من الموارد لتسهيل الأمور لكونه مصرِّفًا Compiler، كما يوفر إطار عمل Svelte الصيغة التفاعلية ‎$store المعروفة باسم الاشتراك التلقائي، إذ ما عليك سوى إضافة العلامة $ إلى المخزن، وسينشئ إطار Svelte الشيفرة البرمجية اللازمة لجعله تفاعليًا تلقائيًا، لذلك يمكن استبدال كتلة الشيفرة البرمجية السابقة بما يلي:

<script>
 import myStore from "./stores.js";
</script>

{$myStore}

سيكون المخزن ‎$myStore تفاعليًا بصورة كاملة، وينطبق ذلك على مخازنك المخصَّصة، فإذا طبَّقتَ التابعَين subscribe()‎ و set()‎ كما سنفعل لاحقًا، فستُطبَّق الصيغة التفاعلية ‎$store على مخازك أيضًا.

لنطبّق ذلك على المكوِّن Alert، لذا عدّل القسمين <script> وشيفرة HTML في الملف Alert.svelte كما يلي:

<script>
  import { alert } from '../stores.js'
</script>

{#if $alert}
<div on:click={() => $alert = ''}>
  <p>{ $alert }</p>
</div>
{/if}

تحقق من تطبيقك مرةً أخرى، وسترى أنه يعمل بصورة أفضل.

أنشأ إطار عمل Svelte في الخلفية شيفرةً برمجيةً للتصريح عن المتغير المحلي ‎$alert وللاشتراك في المخزن alert ولتحديث ‎$alert كلما جرى تعديل محتوى المخزن ولإلغاء الاشتراك عند إلغاء تثبيت المكوِّن، كما سينشِئ التابع alert.set()‎ كلما أسندنا قيمة إلى ‎$alert.

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

الكتابة في المخزن

الكتابة في المخزن هي مجرد مسألة استيراد هذا المخزن وتنفيذ ‎$store = 'new value'‎، لذا لنستخدِم ذلك في المكوِّن Todos.

أولًا، أضِف تعليمة الاستيراد التالية بعد تعليمات الاستيراد الأخرى الموجودة مسبقًا:

import { alert } from '../stores.js'

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

function addTodo(name) {
  todos = [...todos, { id: newTodoId, name, completed: false }]
  $alert = `Todo '${name}' has been added`
}

عدّل الدالة removeTodo()‎ كما يلي:

function removeTodo(todo) {
 todos = todos.filter((t) => t.id !== todo.id)
  todosStatus.focus()             // انقل التركيز إلى عنوان الحالة
  $alert = `Todo '${todo.name}' has been deleted`
}

عدّل الدالة updateTodo()‎ كما يلي:

function updateTodo(todo) {
 const i = todos.findIndex((t) => t.id === todo.id)
  if (todos[i].name !== todo.name)            $alert = `todo '${todos[i].name}' has been renamed to '${todo.name}'`
  if (todos[i].completed !== todo.completed)  $alert = `todo '${todos[i].name}' marked as ${todo.completed ? 'completed' : 'active'}`
  todos[i] = { ...todos[i], ...todo }
}

أضِف الكتلة التفاعلية التالية بعد الكتلة التي تبدأ بالتعليمة let filter = 'all'‎:

$: {
  if (filter === 'all') {
    $alert = 'Browsing all to-dos';
  } else if (filter === 'active') {
    $alert = 'Browsing active to-dos';
  } else if (filter === 'completed') {
    $alert = 'Browsing completed to-dos';
  }
}

أخيرًا، عدّل الكتلتين const checkAllTodos و const removeCompletedTodos كما يلي:

const checkAllTodos = (completed) => {
  todos = todos.map(t => ({...t, completed}))
  $alert = `${completed ? 'Checked' : 'Unchecked'} ${todos.length} to-dos`
}
const removeCompletedTodos = () => {
  $alert = `Removed ${todos.filter((t) => t.completed).length} to-dos`
  todos = todos.filter((t) => !t.completed)
}

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

سيشغّل إطار عمل Svelte التابع alert.set()‎ كلما نفّذنا ‎$alert = ...‎، إذ سيُرسَل إشعار إلى المكوِّن Alert -مثل أيّ مشترك آخر في مخزن التنبيهات alert- عندما يتلقى المخزن قيمةً جديدةً، وسيُحدَّث توصيفه بفضل خاصية التفاعل إطار عمل Svelte، كما يمكننا تطبيق الشيء نفسه ضمن أيّ مكوِّن أو ملف ‎.js.

ملاحظة: لا يمكنك استخدام الصيغة ‎$store خارج مكونات Svelte لأن مصرِّف Svelte لن يستطيع الوصول إلى أيّ شيء خارج مكونات Svelte، لذا يجب عليك الاعتماد على التابعَين store.subscribe()‎ و store.set()‎.

تحسين المكون Alert

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

أولًا، عدّل القسم <script> الخاص بالمكوِّن Alert.svelte كما يلي:

<script>
  import { onDestroy } from 'svelte'
  import { alert } from '../stores.js'

  export let ms = 3000
  let visible
  let timeout

  const onMessageChange = (message, ms) => {
    clearTimeout(timeout)
    if (!message) {               // إخفاء التنبيه إذا كانت الرسالة فارغة
      visible = false
    } else {
      visible = true                                              // إظهار التنبيه
      if (ms > 0) timeout = setTimeout(() => visible = false, ms) // ‫وإخفائه بعد ms ميلي ثانية
    }
  }
  $: onMessageChange($alert, ms)      // شغّل الدالة‫ onMessageChange كلما تغير مخزن alert أو خاصيات ms

  onDestroy(()=> clearTimeout(timeout))           // تأكد من تنظيف المهلة الزمنية

</script>

وعدّل قسم شيفرة HTML الخاصة بالمكوِّن Alert.svelte كما يلي:

{#if visible}
<div on:click={() => visible = false}>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M12.432 0c1.34 0 2.01.912 2.01 1.957 0 1.305-1.164 2.512-2.679 2.512-1.269 0-2.009-.75-1.974-1.99C9.789 1.436 10.67 0 12.432 0zM8.309 20c-1.058 0-1.833-.652-1.093-3.524l1.214-5.092c.211-.814.246-1.141 0-1.141-.317 0-1.689.562-2.502 1.117l-.528-.88c2.572-2.186 5.531-3.467 6.801-3.467 1.057 0 1.233 1.273.705 3.23l-1.391 5.352c-.246.945-.141 1.271.106 1.271.317 0 1.357-.392 2.379-1.207l.6.814C12.098 19.02 9.365 20 8.309 20z"/></svg>
  <p>{ $alert }</p>
</div>
{/if}

ننشئ هنا أولًا الخاصية ms بقيمة افتراضية 3000 (ميلي ثانية)، ثم ننشئ الدالة onMessageChange()‎ التي ستهتم بالتحكم في ما إذا كان التنبيه مرئيًا أم لا، إذ نطلب من إطار عمل Svelte تشغيل هذه الدالة باستخدام ‎$: onMessageChange($alert, ms)‎ عند تغيير المخزن ‎$alert أو الخاصية ms.

كما سننظّف أيّ مهلة زمنية مُعلَّقة عندما يتغير المخزن ‎$alert، فإذا كان المخزن ‎$alert فارغًا، فسنضبط الخاصية visible على القيمة false وسيُزال التنبيه Alert من نموذج DOM؛ أما إذا لم يكن فارغًا، فسنضبط الخاصية visible على القيمة true وسنستخدِم الدالة setTimeout()‎ لمسح التنبيه بعد مدة مقدارها ms ميلي ثانية.

أخيرًا، نتأكد من استدعاء الدالة clearTimeout()‎ باستخدام دالة دورة الحياة onDestroy()‎، كما أضفنا رمز SVG أعلى قسم التنبيه ليبدو أجمل.

جرب التطبيق مرةً أخرى لترى كافة التغييرات.

جعل المكون Alert قابلا للوصول إليه

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

يمكننا التصريح عن منطقة تحتوي على محتوى ديناميكي ويُعلَن عنها من خلال التقنيات المساعدة باستخدام السمة aria-live متبوعة بالإعداد المؤدّب Politeness الذي يُستخدَم لضبط الأولوية التي يجب أن تتعامل بها قارئات الشاشة مع تحديثات تلك المناطق، وتكون الإعدادات المُحتمَلة إما off أو polite أو assertive، كما لديك أيضًا العديد من قيم السمة role المتخصصة والمحدَّدة مسبقًا التي يمكن استخدامها مثل log و status و alert.

ستؤدي إضافة السمة role="alert"‎ إلى الحاوية <div> في حالتنا إلى تنفيذ ما يلي:

<div role="alert" on:click={() => visible = false}>

يُعَدّ اختبار تطبيقاتك باستخدام قارئات الشاشة فكرةً جيدةً لاكتشاف مشاكل الشمولية وسهولة الوصول وللتعود على كيفية استخدام الأشخاص ذوي المشاكل البصرية للويب، كما لديك العديد من الخيارات مثل استخدام قارئ الشاشة NVDA لنظام التشغيل ويندوز وChromeVox للمتصفح كروم، وOrca على نظام لينكس، وVoiceOver لنظام التشغيل Mac OS X وiOS من بين خيارات أخرى.

استخدام مخزن لحفظ المهام

يتيح تطبيقنا الصغير إدارة مهامنا بسهولة تامة، ولكنه عديم الفائدة إذا حصلنا دائمًا على قائمة مهام ثابتة hardcoded نفسها عند إعادة تحميلها، لذا يجب معرفة كيفية استمرار مهامنا لجعلها مفيدة.

يجب أولًا إيجاد طريقة ما لكي يعيد المكوِّن Todos المهام المُحدَّثة إلى المكوِّن الأب، إذ يمكننا إصدار حدث محدَّث مع قائمة المهام، ولكن يُعَدّ ربط المتغير todos أسهل، لذا لنفتح الملف App.svelte ونجرب ذلك.

أضف أولًا السطر التالي بعد مصفوفة todos:

$: console.log('todos', todos)

عدّل بعد ذلك استدعاء المكون Todos كما يلي:

<Todos bind:todos />

ملاحظة: <Todos bind:todos /‎> هو مجرد اختصار للتعليمة <Todos bind:todos={todos} /‎>.

ارجع إلى تطبيقك وحاول إضافة بعض المهام، ثم انتقل إلى طرفية الويب الخاصة بأدوات المطور، حيث ستلاحظ أنّ كل تعديل نجريه على مهامنا ينعكس على المصفوفة todos المُعرَّفة في الملف App.svelte بفضل الموجّه bind.

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

حفظ المهام

لنبدأ باستخدام مخزن عادي قابل للكتابة لحفظ مهامنا.

افتح الملف stores.js وأضِف المخزن التالي بعد المخزن الموجود مسبقًا:

export const todos = writable([])

يجب الآن استيراد المخزن واستخدامه في الملف Alert.svelte، وتذكّر أنه يجب استخدام صيغة المخزن ‎$todos التفاعلية للوصول إلى المهام الآن، لذا عدّل الملف Alert.svelte كما يلي:

<script>
  import Todos from "./components/Todos.svelte";
  import Alert from "./components/Alert.svelte";

  import { todos } from "./stores.js";

  $todos = [
    { id: 1, name: "Create a Svelte starter app", completed: true },
    { id: 2, name: "Create your first component", completed: true },
    { id: 3, name: "Complete the rest of the tutorial", completed: false }
  ];
</script>

<Alert />
<Todos bind:todos={$todos} />

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

كيفية إنشاء مخزن

يمكنك إنشاء مخازنك الخاصة دون الاعتماد على الوحدة svelte/store من خلال تنفيذ مخزن تعمل ميزاته على النحو التالي:

  1. يجب أن يحتوي المخزن على التابع subscribe()‎ الذي يجب أن يقبل دالة اشتراك بوصفها وسيطًا له، ويجب استدعاء جميع دوال الاشتراك النشطة في المخزن عندما تتغير قيمة المخزن.
  2. يجب أن تعيد الدالةُ subscribe()‎ الدالةَ unsubscribe()‎ التي يجب أن توقف الاشتراك عند استدعائها.
  3. يمكن أن يحتوي المخزن اختياريًا على التابع set()‎ الذي يجب أن يقبل قيمة المخزن الجديدة على أساس وسيط له، والذي يستدعي بطريقة متزامنة جميع دوال الاشتراك النشطة في المخزن، كما يُطلَق على المخزن الذي يحتوي على التابع set()‎ اسم مخزن قابل للكتابة.

أولًا، أضِف تعليمات console.log()‎ التالية إلى المكوِّن App.svelte لرؤية مخزن todos ومحتواه أثناء العمل، لذا أضِف الأسطر التالية بعد المصفوفة todos:

console.log('todos store - todos:', todos)
console.log('todos store content - $todos:', $todos)

سترى شيئًا يشبه ما يلي في طرفية الويب عند تشغيل التطبيق:

طرفية الويب عند تشغيل التطبيق

يُعَدّ مخزننا مجرد كائن يحتوي على التوابع subscribe()‎ و set()‎ و update()‎، وتُعَدّ ‎$todos مصفوفة المهام.

إليك مخزن أساسي مُطبَّق من الصفر:

export const writable = (initial_value = 0) => {

  let value = initial_value         // محتوى المخزن
  let subs = []                     // معالجات المشتركين

  const subscribe = (handler) => {
    subs = [...subs, handler]                                 // أضِف معالجًا إلى مصفوفة المشتركين
    handler(value)                                            // استدعِ المعالج باستخدام القيمة الحالية
    return () => subs = subs.filter(sub => sub !== handler)   // إعادة دالة إلغاء الاشتراك
  }

  const set = (new_value) => {
    if (value === new_value) return         // إذا كانت القيمة نفسها، فاخرج
    value = new_value                       // حدّث القيمة
    subs.forEach(sub => sub(value))         // حدّث المشتركين
  }

  const update = (update_fn) => set(update_fn(value))   // حدّث الدالة

  return { subscribe, set, update }       // عَقد المخزن
}

نصرّح في الشيفرة السابقة عن subs والتي هي مصفوفة من المشتركين، كما نضيف في التابع subscribe()‎ المعالج إلى المصفوفة subs ونعيد دالةً ستزيل المعالج من المصفوفة عند تنفيذها، كما نحدّث قيمة المخزن ونستدعي كل معالج عند استدعاء التابع set()‎ من خلال تمرير القيمة الجديدة بوصفها معاملًا.

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

import { writable } from 'svelte/store';
function myStore() {
  const { subscribe, set, update } = writable(0);

  return {
    subscribe,
    addOne: () => update(n => n + 1),
    reset: () => set(0)
  };
}

إذا أصبح تطبيق قائمة المهام معقدًا للغاية، فيمكننا السماح لمخزن المهام بمعالجة كل تعديل للحالة، إذ يمكننا نقل جميع التوابع التي تعدل مصفوفة todos مثل التابعَين addTodo()‎ و removeTodo()‎ وغير ذلك من المكوِّن Todo إلى المخزن، فإذا كان لديك مكان مركزي لتطبيق جميع تعديلات الحالة، فيمكن للمكونات استدعاء هذه التوابع فقط لتعديل حالة التطبيق وعرض المعلومات التي يسمح المخزن بالوصول إليها بصورة تفاعلية، إذ يسهّل وجود مكان فريد لمعالجة تعديلات الحالة التفكيرَ بشأن مشكلات تدفق الحالة وتحديدها.

لن يجبرك إطار عمل Svelte على تنظيم إدارة حالتك بطريقة معينة، وإنما يوفِّر الأدوات لاختيار كيفية معالجتها.

تنفيذ مخزننا المخصص للمهام

لا يُعَدّ تطبيق قائمة المهام معقدًا، لذلك لن ننقل جميع توابع التعديل إلى مكان مركزي، وإنما سنتركها كما هي، وسنركز على استمرار مهامنا بدلًا من ذلك.

ملاحظة: إذا أردت تتبّع هذا المقال باستخدام الأداة Svelte REPL، فلن تتمكن من إكمال هذه الخطوة، إذ تعمل Svelte REPL في بيئة وضع الحماية التي لن تسمح لك بالوصول إلى تخزين الويب، وستحصل على خطأ "العملية غير آمنة The operation is insecure"، وعلى هذا الأساس لا بد من استنساخ المستودع والانتقال إلى المجلد الآتي: mdn-svelte-tutorial/06-stores أو يمكنك تنزيل محتوى المجلد مباشرةً باستخدام الأمر الآتي:

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

يمكنك تطبيق مخزن مخصص يحفظ محتواه في تخزين الويب من خلال استخدام مخزن قابل للكتابة يطبّق ما يلي:

  • يقرأ القيمة من تخزين الويب في البداية ويهيئها بقيمة افتراضية إذا لم تكن موجودةً.
  • يحدّث المخزن نفسه والبيانات الموجودة في التخزين المحلي عند تعديل القيمة.

يدعم تخزين الويب حفظ قيم السلاسل النصية فقط، لذا يجب تحويلها من كائن إلى سلسلة نصية عند الحفظ، والعكس صحيح عند تحميل القيمة من التخزين المحلي.

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

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

import { writable } from 'svelte/store';

export const localStore = (key, initial) => {                 // يتلقى مفتاح التخزين المحلي وقيمة أولية

  const toString = (value) => JSON.stringify(value, null, 2)  // دالة مساعدة
  const toObj = JSON.parse                                    // دالة مساعدة

  if (localStorage.getItem(key) === null) {                   // العنصر غير موجود في التخزين المحلي
    localStorage.setItem(key, toString(initial))              // تهيئة التخزين المحلي بالقيمة الأولية
  }

  const saved = toObj(localStorage.getItem(key))              // تحويل إلى كائن

  const { subscribe, set, update } = writable(saved)          // إنشاء المتجر الأساسي القابل للكتابة

  return {
    subscribe,
    set: (value) => {
      localStorage.setItem(key, toString(value))              // حفظ القيمة في التخزين المحلي كسلسلة نصية
      return set(value)
    },
    update
  }
}

لنشرح الشيفرة البرمجية السابقة:

  • ستكونlocalStore دالةً تقرأ محتواها من تخزين الويب أولًا وتعيد كائنًا مع ثلاث توابع هي subscribe()‎ و set()‎ و update()‎ عند تنفيذها.
  • يجب تحديد مفتاح تخزين الويب وقيمة أولية عند إنشاء دالة localStore جديدة، ثم نتحقق مما إذا كانت القيمة موجودة في تخزين الويب، وننشئها إذا لم يكن الأمر كذلك.
  • نستخدِم التابعَين localStorage.getItem(key)‎ و localStorage.setItem(key, value)‎ لقراءة المعلومات وكتابتها في تخزين الويب، كما نستخدِم الدالتين المساعدتين toString()‎ و toObj()‎ (التي تستخدم التابع JSON.parse()‎) لتحويل القيم.
  • نحوّل بعد ذلك المحتوى المُستلمَ من تخزين الويب من سلسلة نصية إلى كائن، ونحفظ هذا الكائن في مخزننا.
  • أخيرًا، نحدّث تخزين الويب مع تحويل القيمة إلى سلسلة نصية في كل مرة نحدّث فيها محتويات المخزن.

لاحظ أنه كان علينا فقط إعادة تعريف التابع set()‎ من خلال إضافة العملية لحفظ القيمة في تخزين الويب، وما تبقى من الشيفرة البرمجية في أغلبه هو عبارة عن تهيئة وتحويل.

سنستخدِم الآن المخزن المحلي في stores.js لإنشاء مخزن المهام المستمر محليًا، لذا عدّل الملف stores.js كما يلي:

import { writable } from 'svelte/store'
import { localStore } from './localStore.js'

export const alert = writable('Welcome to the to-do list app!')

const initialTodos = [
  { id: 1, name: 'Visit MDN web docs', completed: true },
  { id: 2, name: 'Complete the Svelte Tutorial', completed: false },
]

export const todos = localStore('mdn-svelte-todo', initialTodos)

هيّأنا المخزن لحفظ البيانات في تخزين الويب ليكون تابعًا للمفتاح mdn-svelte-todo باستخدام الدالة الآتية:

 localStore('mdn-svelte-todo', initialTodos)‎

كما ضبطنا بعض المهام لتكون قيمًا أوليةً.

لنتخلص الآن من المهام الثابتة في المكوِّن App.svelte، لذا حدّث محتوياته كما يلي، حيث سنحذف فقط المصفوفة ‎$todos وتعليمات console.log()‎:

<script>
  import Todos from './components/Todos.svelte'
  import Alert from './components/Alert.svelte'

  import { todos } from './stores.js'
</script>

<Alert />
<Todos bind:todos={$todos} />

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

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

كما يمكنك فحص تطبيقك في طرفية أدوات التطوير DevTools من خلال إدخال الأمر localStorage.getItem('mdn-svelte-todo')‎، لذا طبّق بعض التغييرات على تطبيقك مثل الضغط على زر "إلغاء تحديد الكل Uncheck All" وتحقق من محتوى تخزين الويب مرةً أخرى، وستحصل على شيء يشبه ما يلي:

التحقق من محتوى تخزين الويب

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

الانتقالات

لنضِف الآن حركةً إلى التنبيهات، إذ يوفر إطار عمل Svelte وحدةً كاملةً لتعريف الانتقالات transitions والحركات animations لنتمكن من جعل واجهات المستخدِم أكثر جاذبيةً.

يمكن تطبيق الانتقالات باستخدام الموجّه transition:fn الذي يعمل عند دخول عنصر إلى نموذج DOM أو مغادرته بوصفه نتيجةً لتغيير الحالة، إذ تصدّر الوحدة svelte/transition سبع دوال هي fade و blur و fly و slide و scale و draw و crossfade.

لنعطِ المكوِّن Alert انتقالًا transition من النوع fly، لذا افتح الملف Alert.svelte واستورد الدالة fly من الوحدة svelte/transition.

أولًا، ضع تعليمة الاستيراد التالية بعد تعليمات الاستيراد الموجودة مسبقًا:

import { fly } from 'svelte/transition'

ثانيًا، عدّل وسم الفتح <div> كما يلي لاستخدام هذا الانتقال:

<div role="alert" on:click={() => visible = false}
  transition:fly
>

يمكن أن تأخذ الانتقالات معامِلات كما يلي:

<div role="alert" on:click={() => visible = false}
  transition:fly="{{delay: 250, duration: 300, x: 0, y: -100, opacity: 0.5}}"
>

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

جرب تطبيقك مرةً أخرى، وسترى أنّ الإشعارات الآن أكثر جاذبيةً.

ملاحظة: يسمح إطار عمل Svelte بتحسين حجم الحزمة من خلال استبعاد الميزات غير المستخدَمة لكونه مصرِّفًا، فإذا صرّفنا تطبيقنا للإنتاج باستخدام الأمر npm run build، فسيكون وزن الملف public/build/bundle.js أقل بقليل من 22 كيلوبايت، وإذا أزلنا الموجِّه transitions:fly، فإنّ إطار عمل Svelte ذكي بما يكفي لإدراك عدم استخدام الدالة fly، وسينخفض حجم الملف bundle.js إلى 18 كيلوبايت فقط.

ما هذا سوى غيض من فيض، إذ يمتلك إطار عمل Svelte الكثير من الخيارات للتعامل مع الحركات والانتقالات، كما يدعم تحديد انتقالات مختلفة لتطبيقها عند إضافة العنصر أو إزالته من نموذج DOM باستخدام الموجِّه in:fn أو out:fn، ويتيح تعريف انتقالات CSS وجافاسكربت المُخصَّصة، كما لديه العديد من دوال تحسين الحركة Easing لتحديد معدل التغيير بمرور الوقت، ويمكنك إلقاء نظرة على أداة تحسين الحركة البصرية ease visualizer لاستكشاف دوال تحسين الحركة المتاحة المختلفة.

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

cd mdn-svelte-tutorial/07-next-steps

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

npx degit opensas/mdn-svelte-tutorial/07-next-steps

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

الخلاصة

أضفنا في هذا المقال ميزتين جديدتين هما المكوِّن Alert واستمرار المهام todos في تخزين الويب.

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

سنتعرّف في مقال قادم على كيفية إضافة دعم لغة TypeScript إلى تطبيق Svelte، كما سننقل تطبيقنا بالكامل إلى TypeScript للاستفادة من جميع ميزاتها.

ترجمة -وبتصرُّف- للمقال Working with Svelte stores.

اقرأ أيضًا


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

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

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



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

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

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

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


×
×
  • أضف...