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

دعم لغة TypeScript في إطار عمل Svelte


Ola Abbas

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

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

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

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

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

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

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

cd mdn-svelte-tutorial/07-typescript-support

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

npx degit opensas/mdn-svelte-tutorial/07-typescript-support

تذكَّر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير.

ملاحظة: لا يتوفر دعم لغة TypeScript في أداة REPL حتى الآن لسوء الحظ.

لغة TypeScript: استخدام الأنواع الثابتة الاختيارية للغة جافاسكربت

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

أفضل ما في الأمر هو أنّ شيفرة جافاسكربت هي شيفرة TypeScript صالحة، إذ تُعَدّ لغة TypeScript مجموعةً من لغة جافاسكربت، ويمكنك إعادة تسمية معظم ملفات ‎.js إلى ملفات ‎.ts وستعمل مباشرةً، كما ستكون شيفرة TypeScript قادرةً على العمل في أيّ مكان يمكن أن تعمل فيه شيفرة جافاسكربت، إذ تترجِم لغة TypeScript شيفرتنا البرمجية إلى لغة جافاسكربت الصرفة Vanilla JavaScript، وهذا يعني أنها تحلّل شيفرة TypeScript وتنتج شيفرةً مكافئةً بلغة جافاسكربت الصرفة لتشغيلها على المتصفحات.

ملاحظة: إذا كنت مهتمًا بمعرفة كيف تترجم لغة TypeScript الشيفرة البرمجية إلى لغة جافاسكربت، فيمكنك إلقاء نظرةً على TypeScript Playground.

كان دعم TypeScript الميزة الأكثر طلبًا من إطار عمل Svelte لبعض الوقت، لذلك سنوضح لك في القسم التالي كيفية إعداد مشروع Svelte مع دعم TypeScript لتجربته.

فوائد لغة TypeScript

الفوائد الرئيسية للغة TypeScript هي:

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

تحتوي لغة TypeScript على بعض العيوب هي:

  • الأنواع الثابتة غير الصحيحة: تُختبَر الأنواع في وقت التصريف فقط، وتُزال من الشيفرة البرمجية المُنشَأة.
  • المنحنى التعليمي الصعب: تُعَدّ لغة TypeScript صعبة التعلم بالرغم من أنها تُعَدّ مجموعةً من لغة جافاسكربت وليست لغة جديدةً تمامًا، خاصةً إذا لم يكن لديك خبرة على الإطلاق في اللغات الثابتة أو الساكنة مثل جافا أو سي شارب C#‎.
  • مزيد من الشيفرة البرمجية: يجب كتابة مزيد من الشيفرة البرمجية والاحتفاظ بها.
  • عدم وجود بديل للاختبارات الآلية: لا تُعَدّ لغة TypeScript بديلًا حقيقيًا لمجموعة شاملة من الاختبارات الآلية، بالرغم من أنّ الأنواع يمكنها مساعدك في اكتشاف العديد من الأخطاء.
  • الشيفرة المتداولة Boilerplate: يمكن أن يؤدي العمل مع الأنواع والأصناف والواجهات والأنواع المُعمَّمة إلى قواعد شيفرة برمجية متداولة أو مكرَّرة كثيرًا.

يبدو أنّ هناك إجماعًا واسعًا على أن لغة TypeScript مناسبة للمشاريع واسعة النطاق حيث يعمل العديد من المطورين على قاعدة الشيفرة البرمجية نفسها، إذ تستخدِمها حاليًا العديد من المشاريع الكبيرة مثل Angular 2 و Vue 3 و Ionic و Visual Studio Code و Jest ومصرِّف إطار Svelte، لكن يفضِّل بعض المطورين استخدامها في المشاريع الصغيرة أيضًا مثل المشروع الذي نطوّره حاليًا.

إنشاء مشروع Svelte باستخدام لغة TypeScript من الصفر

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

npx degit sveltejs/template svelte-typescript-app

cd svelte-typescript-app

node scripts/setupTypeScript.js

تؤدي هذه الأوامر إلى إنشاء مشروع أولي يتضمن دعم TypeScript والذي يمكنك تعديله لاحقًا كما يحلو لك، كما يجب بعد ذلك إخبار مدير الحزم npm بتنزيل الاعتماديات Dependencies وبدء المشروع في وضع التطوير كما يلي:

npm install

npm run dev

إضافة دعم TypeScript إلى مشروع Svelte قائم مسبقا

يمكنك إضافة دعم TypeScript إلى مشروع Svelte قائم من خلال اتباع هذه التعليمات، أو يمكنك بدلًا من ذلك تنزيل ملف setupTypeScript.js إلى المجلد scripts ضمن المجلد الجذر لمشروعك، ثم شغّل الأمر الآتي:

 node scripts/setupTypeScript.js

كما يمكنك استخدام الأداة degit لتنزيل السكربت، وهذا ما سنفعله لبدء نقل تطبيقنا إلى لغة TypeScript.

ملاحظة: تذكَّر أنه يمكنك تشغيل الأمر الآتي:

 npx degit opensas/mdn-svelte-tutorial/07-typescript-support svelte-todo-typescript 

للحصول على تطبيق قائمة المهام الكامل بلغة جافاسكربت قبل البدء في نقله إلى لغة TypeScript.

انتقل إلى المجلد الجذر للمشروع وأدخِل الأوامر التالية:

npx degit sveltejs/template/scripts scripts       # نزِّل ملف السكربت في مجلد السكربتات

node scripts/setupTypeScript.js                   # شغّله
Converted to TypeScript.

كما يجب إعادة تشغيل مدير الاعتماديات للبدء.

npm install                                       # نزّل الاعتماديات الجديدة

npm run dev                                       # شغّل التطبيق في وضع التطوير

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

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

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

يمكنك البدء في استخدام لغة TypeScript من مكونات Svelte بعد إعدادها من خلال إضافة <script lang='ts'‎> في بداية القسم script، وما عليك سوى تغيير امتداد الملف من ‎.js إلى ‎.ts لاستخدامه في ملفات جافاسكربت العادية. كما يجب تحديث أيّ تعليمات import استيراد مقابلة، إذ لا يجب أن تضمّن الامتداد ‎.ts في تعليمات الاستيراد لأن لغة TypeScript تحذف الامتدادات.

ملاحظة: استخدام TypeScript في أقسام توصيف Markup المكونات غير متاح حتى الآن، لذا يجب استخدام لغة جافاسكربت في التوصيف واستخدام لغة TypeScript في القسم <script lang='ts'‎>.

تحسين تجربة المطور باستخدام لغة TypeScript

توفِّر لغة TypeScript محرّرات شيفرات برمجية وبيئات IDE مع الكثير من المعلومات للسماح لها بتقديم تجربة تطوير أفضل، وسنستخدِم المحرر Visual Studio Code لإجراء اختبار سريع ونرى كيف يمكننا الحصول على تلميحات الإكمال التلقائي وفحص الأنواع أثناء كتابة المكونات.

ملاحظة: إذا لم ترغب في استخدام VS Code، فسنقدِّم إرشادات لاستخدام التحقق من أخطاء TypeScript من الطرفية بدلًا من ذلك لاحقًا.

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

ملاحظة: تأكد من أنك تستخدِم الإضافة Svelte for VS Code وليس Svelte القديمة لصاحبها James Birtles التي جرى إيقافها، فإذا ثبّتها، فيجب عليك إلغاء تثبيتها وتثبيت إضافة Svelte الرسمية بدلًا من ذلك.

لنفترض أنك ضمن تطبيق VS Code، اكتب code .‎ من جذر مجلد مشروعك، حيث تخبر هذه النقطة اللاحقة شيفرة VS بفتح المجلد الحالي لفتح محرر الشيفرة البرمجية، كما سيخبرك المحرّر VS Code بوجود إضافات موصَى بها للتثبيت.

نافذة محرّر VS Code حول وجود إضافات موصَى بها للتثبيت

سيؤدي النقر على "تثبيت الكل Install all" إلى تثبيت الإضافة Svelte for VS Code.

تثبيت الإضافة Svelte for VS Code

كما يمكننا أن نرى أنّ الملف setupTypeScript.js أجرى بعض التغييرات على مشروعنا، إذ أُعيدت تسمية الملف main.js إلى main.ts، مما يعني أنّ شيفرة VS يمكن أن توفر معلومات التمرير حول مكونات Svelte:

إجراء الملف setupTypeScript.js لبعض التغييرات على المشروع

سنحصل على ميزة التحقق من الأنواع مجانًا، فإذا مررنا خاصيةً غير معروفة في معامِل الخيارات لدالة البناء App مثل كتابة الخطأ المطبعي traget بدلًا من target، فستعطي TypeScript خطأً كما يلي:

ميزة التحقق من الأنواع مجانًا في TypeScript

أضاف السكربت setupTypeScript.js في المكوِّن App.svelte السمة lang="ts"‎ إلى الوسم <script>. كما لن نحتاج في كثير من الحالات لتحديد الأنواع للحصول على مساعدة الشيفرة البرمجية بفضل ميزة استنتاج الأنواع، فإذا أضفتَ الخاصية ms إلى استدعاء المكوِّن Alert مثلًا، فستستنتج لغة TypeScript من القيمة الافتراضية أنّ الخاصية ms يجب أن تكون عددًا.

إضافة الخاصية ms إلى استدعاء المكوِّن Alert

وإذا مرّرت شيئًا ليس عددًا، فسيظهر خطأ كما يلي:

ميزة استنتاج الأنواع في TypeScript

يحتوي قالب التطبيق على سكربت تحقق check مُعَدّ لتشغيل أداة svelte-check للتحقق من شيفرتك البرمجية، إذ تسمح هذه الحزمة باكتشاف الأخطاء والتحذيرات التي يعرضها محرر الشيفرات البرمجية من سطر الأوامر، مما يجعلها مفيدة جدًا لتشغيلها في خط أنابيب تكامل مستمر continuous integration أو CI اختصارًا، لذا شغّل الأمر npm run check لفحص أجزاء شيفرة CSS غير المُستخدَمة، وإعادة تلميحات الشمولية A11y وأخطاء تصريف شيفرة TypeScript.

إذا شغّلتَ الأمر npm run check في هذه الحالة في طرفية VS Code أو طرفية جهازك، فسيظهر الخطأ التالي:

تشغيل الأمر npm run check  في طرفية VS Code

وإذا شغّلته من الطرفية المدمجة في VS Code التي يمكنك فتحها باستخدام الاختصار "Ctrl + `‎"، فسينقلك الضغط مع المفتاح Cmd أو Ctrl على اسم الملف إلى السطر الذي يحتوي على الخطأ.

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

إنشاء نوع مخصص

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

أنشئ المجلد types ضمن المجلد src، ثم أضف الملف todo.type.ts ضمنه وضع المحتوى التالي فيه:

export type TodoType = {
  id: number
  name: string
  completed: boolean
}

ملاحظة: يستخدِم قالبُ Svelte المعالجَ المُسبَق svelte-preprocess 4.0.0 لدعم لغة TypeScript، كما يجب استخدام صيغة الأنواع export/import لاستيراد الأنواع والواجهات من هذا الإصدار فصاعدًا.

سنستخدِم الآن النوع TodoType في المكوِّن Todo.svelte، لذا أضِف أولًا السمة lang="ts"‎ إلى الوسم <script>، لنستورد النوع ولنستخدِمه للتصريح عن الخاصية todo، لذا استبدل السطر export let todo بما يلي:

import type { TodoType } from "../types/todo.type";

export let todo: TodoType;

ملاحظة: تذكّر أنه يجب حذف الامتداد عند استيراد ملف ‎.ts.

سننشئ الآن نسخةً من المكوِّن Todo من الملف Todos.svelte مع كائن حرفي بوصفه معامِلًا قبل استدعاء المكوِّن MoreActions كما يلي:

<hr />

<Todo todo={ { name: 'a new task with no id!', completed: false } } />

<!-- MoreActions -->
<MoreActions {todos}

أضف السمة lang='ts'‎ إلى الوسم <script> الخاص بالمكوِّن Todos.svelte لمعرفة كيفية استخدام التحقق من الأنواع الذي حددناه، ولكن سنحصل على الخطأ التالي:

إضافة lang='ts'‎ إلى <script> لمكوِّن Todos.svelte

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

  1. أزِل المهمة التي تسبب الأخطاء والسمة lang='ts'‎ من الملف Todos.svelte.
  2. أزِل استيراد النوع TodoType والسمة lang='ts'‎ من الملف Todos.svelte.

نقل تطبيق قائمة المهام إلى لغة TypeScript

أصبحنا الآن جاهزين لبدء نقل تطبيق قائمة المهام للاستفادة من جميع الميزات التي توفرها لغة TypeScript. لنبدأ بتشغيل سكربت التحقق في وضع المراقبة ضمن جذر المشروع:

npm run check -- --watch

يجب أن ينتج ما يلي:

svelte-check "--watch"

Loading svelte-check in workspace: ./svelte-todo-typescript
Getting Svelte diagnostics...
====================================
svelte-check found no errors and no warnings

لاحظ أنه إذا استخدَمتَ محرر شيفرات داعم مثل المحرّر VS Code، فستكون الطريقة البسيطة لبدء نقل مكوِّن Svelte هي بإضافة <script lang='ts'‎> فقط في أعلى المكوِّن والبحث عن تلميحات ثلاثية النقاط كما يلي:

استخدام محرر شيفرات

المكون Alert.svelte

لنبدأ بالمكوِّن Alert.svelte.

أضِف السمة lang="ts"‎ إلى الوسم <script> الخاص بالمكوِّن Alert.svelte، إذ سترى بعض التحذيرات في خرج السكربت check كما يلي:

$ npm run check -- --watch
> svelte-check "--watch"

./svelte-todo-typescript
Getting Svelte diagnostics...
====================================

./svelte-todo-typescript/src/components/Alert.svelte:8:7
Warn: Variable 'visible' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
  let visible

./svelte-todo-typescript/src/components/Alert.svelte:9:7
Warn: Variable 'timeout' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
  let timeout

./svelte-todo-typescript/src/components/Alert.svelte:11:28
Warn: Parameter 'message' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
Change = (message, ms) => {

./svelte-todo-typescript/src/components/Alert.svelte:11:37
Warn: Parameter 'ms' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
(message, ms) => {

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

export let ms = 3000

  let visible: boolean
  let timeout: number

  const onMessageChange = (message: string, ms: number) => {
    clearTimeout(timeout)
    if (!message) {               // إخفاء التنبيه إذا كانت الرسالة فارغة

ملاحظة: ليست هناك حاجة لتحديد نوع ms في التعليمة export let ms:number = 3000، لأن لغة TypeScript تستنتجه مباشرةً من قيمته الافتراضية.

المكون MoreActions.svelte

سنطبّق الآن الشيء نفسه على المكوِّن MoreActions.svelte.

أضِف السمة lang='ts'‎، وستحذِّرنا لغة TypeScript من الخاصية todos والمتغير t في الاستدعاء todos.filter((t) =>...)‎.

Warn: Variable 'todos' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
  export let todos

Warn: Parameter 't' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
  $: completedTodos = todos.filter((t) => t.completed).length

سنستخدِم النوع TodoType الذي عرّفناه سابقًا لإعلام لغة TypeScript أنّ الخاصية todos هي مصفوفة TodoType، لذا استبدل export let todos بما يلي:

import type { TodoType } from "../types/todo.type";

export let todos: TodoType[];

لاحظ أنّ لغة TypeScript يمكنها الآن أن تستنتج أنّ المتغير t في الاستدعاء todos.filter(t => t.completed)‎ من النوع TodoType، ولكن يمكننا تحديده كما يلي إذا اعتقدنا أنّ ذلك يسهل قراءة شيفرتنا:

$: completedTodos = todos.filter((t: TodoType) => t.completed).length;

ستكون لغة TypeScript قادرةً على استنتاج نوع المتغير التفاعلي بصورة صحيحة، ولكن يمكن أن يظهر الخطأ "implicitly has an 'any' type" عند العمل مع الإسنادات التفاعلية، لذا يمكنك في هذه الحالات التصريح عن نوع المتغير في تعليمة مختلفة كما يلي:

let completeTodos: number;
$: completedTodos = todos.filter((t: TodoType) => t.completed).length;

لا يمكنك تحديد النوع في الإسناد التفاعلي نفسه، فالتعليمة التالي غير صالحة:

$: completedTodos: number = todos.filter[...]‎

المكون FilterButton.svelte

أضف السمة lang='ts'‎ إلى الوسم <script> مثل العادة وستلاحظ عدم وجود تحذيرات، إذ تستنتج لغة TypeScript نوع متغير الترشيح filter من قيمته الافتراضية، ولكننا نعلم أنه يملك ثلاث قيم صالحة فقط هي: جميع المهام all والمهام النشط active والمهام المكتملة completed، حيث يمكننا السماح للغة TypeScript بالتعرف على هذه القيم من خلال إنشاء مُرشح filter من النوع الثوابت المتعددة enum.

أنشئ بعدها ملفًا بالاسم filter.enum.ts في المجلد types، وضع فيه المحتويات التالية:

export enum Filter {
  ALL = 'all',
  ACTIVE = 'active',
  COMPLETED = 'completed',
}

سنستخدِم ذلك الآن في المكوِّن FilterButton، لذا استبدل محتوى الملف FilterButton.svelte بما يلي:

<!-- components/FilterButton.svelte -->
<script lang='ts'>
  import { Filter } from '../types/filter.enum'

  export let filter: Filter = Filter.ALL
</script>

<div class="filters btn-group stack-exception">
  <button class="btn toggle-btn" class:btn__primary={filter === Filter.ALL} aria-pressed={filter === Filter.ALL} on:click={()=> filter = Filter.ALL} >
    <span class="visually-hidden">Show</span>
    <span>All</span>
    <span class="visually-hidden">tasks</span>
  </button>
  <button class="btn toggle-btn" class:btn__primary={filter === Filter.ACTIVE} aria-pressed={filter === Filter.ACTIVE} on:click={()=> filter = Filter.ACTIVE} >
    <span class="visually-hidden">Show</span>
    <span>Active</span>
    <span class="visually-hidden">tasks</span>
  </button>
  <button class="btn toggle-btn" class:btn__primary={filter === Filter.COMPLETED} aria-pressed={filter === Filter.COMPLETED} on:click={()=> filter = Filter.COMPLETED} >
    <span class="visually-hidden">Show</span>
    <span>Completed</span>
    <span class="visually-hidden">tasks</span>
  </button>
</div>

استوردنا فقط المرشح Filter من النوع enum واستخدمناه بدلًا من قيم السلاسل النصية التي استخدمناها سابقًا.

المكون Todos.svelte

سنستخدِم المرشح Filter من النوع enum في المكوِّن Todos.svelte.

أضِف أولًا السمة lang='ts'‎ إليه، ثم استورد المرشح Filter من النوع enum، لذا أضِف تعليمة الاستيراد التالية بعد تعليمات الاستيراد الموجودة مسبقًا:

import { Filter } from "../types/filter.enum";

سنستخدِمه الآن في أيّ مكان نشير فيه إلى المرشّح Filter الحالي، لذا استبدل الكتلتين المتعلقتين به بما يلي:

let filter: Filter = Filter.ALL;
const filterTodos = (filter: Filter, todos) =>
  filter === Filter.ACTIVE
    ? todos.filter((t) => !t.completed)
    : filter === Filter.COMPLETED
    ? todos.filter((t) => t.completed)
    : todos;

$: {
  if (filter === Filter.ALL) $alert = "Browsing all todos";
  else if (filter === Filter.ACTIVE) $alert = "Browsing active todos";
  else if (filter === Filter.COMPLETED) $alert = "Browsing completed todos";
}

سيظل السكربت check يعطينا بعض التحذيرات من المكوِّن Todos.svelte، فلنصلح ذلك من خلال استيراد النوع TodoType وإعلام لغة TypeScript أنّ المتغير todos هو مصفوفة من النوع TodoType، لذا استبدل السطر export let todos = []‎ بالسطرين التاليين:

import type { TodoType } from "../types/todo.type";

export let todos: TodoType[] = [];

سنحدّد بعد ذلك جميع الأنواع المفقودة، إذ يُعَدّ المتغير todosStatus -الذي استخدمناه للوصول برمجيًا إلى التوابع التي يمكن الوصول إليها من المكوِّن TodosStatus- من النوع TodosStatus، كما ستكون كل مهمة todo من النوع TodoType، لذا عدّل القسم <script> ليبدو كما يلي:

<script lang='ts'>
  import FilterButton from './FilterButton.svelte'
  import Todo from './Todo.svelte'
  import MoreActions from './MoreActions.svelte'
  import NewTodo from './NewTodo.svelte'
  import TodosStatus from './TodosStatus.svelte'
  import { alert } from '../stores'

  import { Filter } from '../types/filter.enum'

  import type { TodoType } from '../types/todo.type'

  export let todos: TodoType[] = []

  let todosStatus: TodosStatus                   //  ‫مرجع إلى نسخة TodosStatus 

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

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

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

  function updateTodo(todo: TodoType) {
    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: Filter = Filter.ALL
  const filterTodos = (filter: Filter, todos: TodoType[]) =>
    filter === Filter.ACTIVE ? todos.filter(t => !t.completed) :
    filter === Filter.COMPLETED ? todos.filter(t => t.completed) :
    todos

  $: {
    if (filter === Filter.ALL)               $alert = 'Browsing all todos'
    else if (filter === Filter.ACTIVE)       $alert = 'Browsing active todos'
    else if (filter === Filter.COMPLETED)    $alert = 'Browsing completed todos'
  }

  const checkAllTodos = (completed: boolean) => {
    todos = todos.map(t => ({...t, completed}))
    $alert = `${completed ? 'Checked' : 'Unchecked'} ${todos.length} todos`
  }
  const removeCompletedTodos = () => {
    $alert = `Removed ${todos.filter(t => t.completed).length} todos`
    todos = todos.filter(t => !t.completed)
  }
</script>

المكون TodosStatus.svelte

نواجه الأخطاء التالية المتعلقة بتمرير todos إلى المكوِّنين TodosStatus.svelte و Todo.svelte:

./src/components/Todos.svelte:70:39
Error: Type 'TodoType[]' is not assignable to type 'undefined'. (ts)
  <TodosStatus bind:this={todosStatus} {todos} />

./src/components/Todos.svelte:76:12
Error: Type 'TodoType' is not assignable to type 'undefined'. (ts)
     <Todo {todo}

تظهر هذه الأخطاء لأن الخاصية todos في المكوِّن TodosStatus ليس لها قيمة افتراضية، لذلك استنتجت لغة TypeScript أنها من النوع غير المعرَّف undefined، وهو غير متوافق مع مصفوفة من النوع TodoType، كما يحدث الشيء نفسه مع المكوِّن Todo، إذًا لنصلح هذه الأخطاء.

افتح الملف TodosStatus.svelte وأضِف السمة lang='ts'‎ ثم استورد النوع TodoType وصرّح عن الخاصية todos على أنها مصفوفة من النوع TodoType، لذا استبدل السطر الأول من القسم <script> بما يلي:

import type { TodoType } from "../types/todo.type";

export let todos: TodoType[];

كما سنحدّد المتغير headingEl -الذي استخدمناه لربط وسم العنوان- بوصفه من النوع HTMLElement، لذا عدّل السطر let headingEl كما يلي:

let headingEl: HTMLElement;

أخيرًا، ستلاحظ الخطأ التالي المتعلق بالمكان الذي ضبطنا فيه السمة tabindex، لأن لغة TypeScript تتحقق من نوع العنصر <h2> وتتوقع أن تكون السمة tabindex من النوع العددي number.

تحقق لغة TypeScript من نوع العنصر <h2>

يمكن إصلاح هذا الخطأ من خلال استبدال tabindex="-1"‎ بالشكل tabindex={-1}‎ كما يلي:

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

يمكن أن تمنعنا لغة TypeScript باستخدام هذه الطريقة من إسناد هذه السمة إلى متغير من نوع السلسلة النصية بصورة غير صحيحة.

المكون NewTodo.svelte

أضِف السمة lang='ts'‎ مثل العادة، حيث سيشير التحذير إلى أنه يجب تحديد نوع للمتغير nameEl، لذا اضبط نوعه على النوع HTMLElement كما يلي:

let nameEl: HTMLElement; // reference to the name input DOM node

كما يجب تحديد النوع الصحيح للمتغير autofocus، لذا عدّل تعريفه إلى ما يلي:

export let autofocus: boolean = false;

المكون Todo.svelte

تظهر التحذيرات الوحيدة التي يصدرها الأمر npm run check من خلال استدعاء المكوِّن Todo.svelte، إذًا لنصلحها.

افتح الملف Todo.svelte وأضِف السمة lang='ts'‎ ثم استورد النوع TodoType واضبط نوع الخاصية todo، أي استبدل السطر export let todo بما يلي:

import type { TodoType } from "../types/todo.type";

export let todo: TodoType;

التحذير الأول الذي حصلنا عليه هو أنّ لغة TypeScript تُعلِمنا بأنه يجب تحديد نوع المتغير updatedTodo الخاص بالدالة update()‎، كما يمكن أن يكون هذا صعبًا بعض الشيء لأن المتغير updatedTodo يحتوي فقط على سمات المهمة todo التي جرى تحديثها، وهذا يعني أنها ليست مهمة todo كاملة، فهي تحتوي فقط على مجموعة فرعية من خاصيات المهام، لذلك توفر لغة TypeScript العديد من الأدوات المساعدة الخاصة بالأنواع لتسهيل تطبيق هذه التحويلات الشائعة، حيث سنستخدِم الآن الأداة المساعدة Partial<T>‎ التي تتيح تمثيل جميع المجموعات الفرعية من نوع معيّن، وتعيد نوعًا جديدًا بناءً على النوع T، إذ تكون كل خاصية من خصائص T اختياريةً، كما سنستخدِم هذه الأداة في الدالة update()‎، لذلك عدّلها كما يلي:

function update(updatedTodo: Partial<TodoType>) {
  todo = { ...todo, ...updatedTodo }; // تطبيق التعديلات على المهمة
  dispatch("update", todo); // إصدار حدث التحديث
}

بذلك نخبر لغة TypeScript أنّ المتغير updatedTodo سيحتوي على مجموعة فرعية من الخاصيات ذات النوع TodoType.

تخبرنا الآن الأداة svelte-check أنه يجب تعريف نوع معامِلات دالة الإجراء:

./07-next-steps/src/components/Todo.svelte:45:24
Warn: Parameter 'node' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
  const focusOnInit = (node) => node && typeof node.focus === 'function' && node.focus()

./07-next-steps/src/components/Todo.svelte:47:28
Warn: Parameter 'node' implicitly has an 'any' type, but a better type may be inferred from usage. (ts)
  const focusEditButton = (node) => editButtonPressed && node.focus()

يجب فقط تعريف المعامل node ليكون من النوع HTMLElement، لذا استبدل النسخة الأولى من المعامِل node بالشكل node: HTMLElement في السطرين السابقين المشار إليهما.

الملف actions.js

أعد تسمية الملف actions.js إلى actions.ts وأضِف نوع المعامِل node كما يلي:

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

عدّل الآن الملفين Todo.svelte و NewTodo.svelte حيث نستورد ملف الإجراءات actions، وتذكّر أن تعليمات الاستيراد في لغة TypeScript لا تتضمن امتداد الملف.

import { selectOnFocus } from "../actions";

ترحيل المخازن Stores إلى لغة TypeScript

يجب الآن ترحيل الملفين stores.js و localStore.js إلى لغة TypeScript.

سيتحقق السكربت npm run check الذي يستخدِم الأداة svelte-check من ملفات ‎.svelte الخاصة بتطبيقنا، فإذا أردتَ التحقق من ملفات ‎.ts، فيمكنك تشغيل الأمر npm run check && npx tsc --noemit الذي يخبر مصرِّف TypeScript بالتحقق من الأخطاء دون إنشاء ملفات الخرج ‎.js، كما يمكنك إضافة سكربت إلى الملف package.json الذي يشغّل ذلك الأمر.

سنبدأ بترحيل الملف stores.js:

أعِد تسمية الملف إلى stores.ts ثم اضبط نوع المصفوفة initialTodos إلى النوع TodoType[]‎ كما يلي:

// stores.ts
import { writable } from "svelte/store";
import { localStore } from "./localStore.js";
import type { TodoType } from "./types/todo.type";

export const alert = writable("Welcome to the To-Do list app!");

const initialTodos: TodoType[] = [
  { 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);

تذكّر تحديث تعليمات الاستيراد في الملفات App.svelte و Alert.svelte وTodos.svelte، وأزِل فقط الامتداد ‎.js كما يلي:

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

لننتقل الآن إلى الملف localStore.js، وعدّل تعليمة الاستيراد في الملف stores.ts كما يلي:

import { localStore } from "./localStore";

أعِد تسمية الملف إلى localStore.ts، حيث تخبرنا لغة TypeScript بأنه يجب تحديد نوع المتغيرات key و initial و value، إذ يجب أن يكون المتغير الأول -وهو مفتاح تخزين الويب المحلي- سلسلةً نصيةً، لكن يجب أن يكون المتغيران initial و value أيّ كائن يمكن تحويله إلى سلسلة JSON صالحة باستخدام التابع JSON.stringify، مما يعني أن أيّ كائن جافاسكربت له قيود معينة مثل undefined والدوال والرموز التي ليست قيم JSON صالحة، لذا سننشئ النوع JsonValue لتحديد هذه الشروط. أنشئ الملف json.type.ts في المجلد types وضع فيه المحتوى التالي:

export type JsonValue =
  | string
  | number
  | boolean
  | null
  | JsonValue[]
  | { [key: string]: JsonValue };

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

سنستورد النوع JsonValue وسنستخدِمه وفقًا لذلك، لذا عدّل الملف localStore.ts كما يلي:

// localStore.ts
import { writable } from "svelte/store";

import type { JsonValue } from "./types/json.type";

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

  const toString = (value: JsonValue) => 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: JsonValue) => {
      localStorage.setItem(key, toString(value)); // حفظ القيمة في التخزين المحلي كسلسلة نصية
      return set(value);
    },
    update,
  };
};

إذا حاولنا الآن إنشاء مخزن localStore مع شيء لا يمكن تحويله إلى صيغة JSON باستخدام التابع JSON.stringify()‎ مثل كائن مع دالة بوصفها خاصيةً له، فسيعطي المحرر VS Code أو validate خطأً كما يلي:

نتيجة إنشاء مخزن localStore مع شيء لا يمكن تحويله إلى صيغة JSON

سيعمل الملف localStore.ts مع صيغة الاشتراك التلقائي ‎$store، فإذا حاولنا حفظ قيمة غير صالحة في مخزن todos باستخدام صيغة ‎$store كما يلي:

<!-- App.svelte -->
<script lang="ts">
  import Todos from "./components/Todos.svelte";
  import Alert from "./components/Alert.svelte";

  import { todos } from "./stores";

  // ‫هذه قيمة غير صالحة، فالمحتوى لا يمكن تحويله إلى صيغة JSON باستخدام JSON.stringify
  $todos = { handler: () => {} };
</script>

فسيعطي سكربت التحقق الخطأ التالي:

> npm run check

Getting Svelte diagnostics...
====================================

./svelte-todo-typescript/src/App.svelte:8:12
Error: Argument of type '{ handler: () => void; }' is not assignable to parameter of type 'JsonValue'.
  Types of property 'handler' are incompatible.
    Type '() => void' is not assignable to type 'JsonValue'.
      Type '() => void' is not assignable to type '{ [key: string]: JsonValue; }'.
        Index signature is missing in type '() => void'. (ts)
 $todos = { handler: () => {} }

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

حوّلنا بذلك تطبيقنا بالكامل ليستخدِم لغة TypeScript.

حماية المخازن من الأخطاء باستخدام الأنواع المعممة

نقلنا المخازن إلى لغة TypeScript، ولكن يمكننا تنفيذ عمل أفضل، فلا يجب علينا تخزين أيّ نوع من القيم، لأننا نعلم أنّ مخزن التنبيه alert يجب أن يحتوي على رسائل نصية، كما يجب أن يحتوي متجر المهام todos على مصفوفة من النوع TodoType وما إلى ذلك، ويمكننا السماح للغة TypeScript بفرض ذلك باستخدام الأنواع المُعمَّمة Generics في لغة TypeScript.

الأنواع المعممة Generics في لغة TypeScript

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

تُمرَّر الأنواع المُعمَّمة بوصفها معامِلات باستخدام صيغة خاصة، إذ تُحدَّد بين قوسَي زاوية، ويُشار إليها حسب الاصطلاح بحرف واحد كبير، كما تسمح الأنواع المُعمَّمة بالتقاط الأنواع التي يوفرها المستخدِم لاستخدامها لاحقًا، ولنرى مثالًا سريعًا يمثل الصنف Stack البسيط الذي يتيح دفع push العناصر وسحبها pop كما يلي:

export class Stack {
  private elements = []

  push = (element) => this.elements.push(element)

  pop() {
    if (this.elements.length === 0) throw new Error('The stack is empty!')
    return this.elements.pop()
  }
}

تكون elements في هذه الحالة مصفوفةً من النوع any، وبالتالي يستقبل ويعيد التابعان push()‎ و pop()‎ متغيرًا من النوع any، لذا يمكنك تطبيق ما يلي:

const anyStack = new Stack();

anyStack.push(1);
anyStack.push("hello");

لكن إذا أردنا الحصول على الصنف Stack الذي يعمل فقط مع نوع السلسلة النصية string، فيمكننا تطبيق ما يلي:

export class StringStack {
  private elements: string[] = []

  push = (element: string) => this.elements.push(element)

  pop(): string {
    if (this.elements.length === 0) throw new Error('The stack is empty!')
    return this.elements.pop()
  }
}

أما إذا أردنا التعامل مع الأعداد، فسنضطر بعد ذلك إلى تكرار الشيفرة وإنشاء الصنف NumberStack، وإذا أردنا التعامل مع مجموعة من الأنواع التي لا نعرفها بعد ويجب أن يعرّفها المستخدِم، فيمكننا استخدام الأنواع المُعمَّمة Generics.

إليك الصنف Stack الذي أعيد تطبيقه باستخدام الأنواع المُعمَّمة Generics:

export class Stack<T> {
  private elements: T[] = []

  push = (element: T): number => this.elements.push(element)

  pop(): T {
    if (this.elements.length === 0) throw new Error('The stack is empty!')
    return this.elements.pop()
  }
}

نعرِّف النوع T المُعمَّم ثم نستخدِمه كما نستخدِم أيّ نوع آخر، إذ تُعَدّ elements الآن مصفوفةً من النوع T، كما يستقبل ويعيد كل من التابعين push()‎ و pop()‎ متغيرًا من النوع T.

إليك الطريقة التي نستخدِم بها النوع المُعمَّم Stack:

const numberStack = new Stack<number>()
numberStack.push(1)

تعرف لغة TypeScript الآن أنّ Stack يمكنه قبول الأعداد فقط وسيعطي خطأً إذا حاولنا دفع أيّ شيء آخر:

 قبول Stack للأعداد فقط

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

استخدام مخازن Svelte مع الأنواع المعممة

تدعم مخازن Svelte الأنواع المُعمَّمة، إذ يمكننا الاستفادة من ميزة استنتاج الأنواع المُعمَّمة دون لمس شيفرتنا البرمجية، فإذا فتحت الملف Todos.svelte وأسندتَ النوع number إلى المخزن ‎$alert، فستحصل على الخطأ التالي:

استخدام مخازن Svelte مع الأنواع المعممة

لأنه عندما عرّفنا المخزن alert في الملف stores.ts باستخدام ما يلي:

export const alert = writable("Welcome to the To-Do list app!");

استنتجت لغة TypeScript أنّ النوع المُعمَّم هو سلسلة نصية string، فإذا أردنا أن نكون صريحين بشأن ذلك، فيمكننا كتابة ما يلي:

export const alert = writable < string > "Welcome to the To-Do list app!";

سنجعل الآن المخزن localStore يدعم الأنواع المُعمَّمة، وتذكّر أننا عرّفنا النوع JsonValue لمنع استخدام المخزن localStore مع قيم لا يمكن استمرارها باستخدام التابع JSON.stringify()‎.

نريد الآن أن يتمكن مستخدِمو المخزن localStore من تحديد نوع البيانات المستمرة، ولكن يجب استخدام النوع JsonValue بدلًا من العمل مع أيّ نوع، لذا سنحدد ذلك بقيد مُعمَّم كما يلي:

export const localStore = <T extends JsonValue>(key: string, initial: T)

سنعرّف النوع المُعمَّم T وسنحدِّد أنه يجب أن يكون متوافقًا مع النوع JsonValue، ثم سنستخدِم النوع T بطريقة مناسبة، وسيكون الملف localStore.ts كما يلي:

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

import type { JsonValue } from './types/json.type'

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

  const toString = (value: T) => 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<T>(saved)                   // إنشاء المتجر الأساسي القابل للكتابة

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

تعرف لغة TypeScript أن المخزن ‎$todos يجب أن يحتوي على مصفوفة من النوع TodoType بفضل ميزة استنتاج الأنواع المُعمَّمة:

المخزن ‎$todos يجب أن يحتوي على مصفوفة من النوع TodoType

كما ذكرنا سابقًا إذا أردنا أن نكون صريحين، فيمكننا ذلك في الملف stores.ts كما يلي:

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

export const todos = localStore<TodoType[]>('mdn-svelte-todo', initialTodos)

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

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

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

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

تذكَّر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير.

ملاحظة: كما قلنا سابقًا لا يتوفر دعم لغة TypeScript في أداة REPL حتى الآن لسوء الحظ.

الخلاصة

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

رأينا كيفية العمل مع المحرّر Visual Studio Code والإضافة Svelte للحصول على ميزات مثل التحقق من الأنواع والإكمال التلقائي، واستخدمنا الأداة svelte-check لفحص مشاكل TypeScript من سطر الأوامر، كما سنتعلّم في المقال التالي كيفية تصريف ونشر تطبيقنا للإنتاج، وسنرى موارد التعلم المتاحة عبر الإنترنت للمضي قدمًا في تعلم إطار عمل Svelte.

ترجمة -وبتصرُّف- للمقال TypeScript support in Svelte.

اقرأ أيضًا


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

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

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



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

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

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

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


×
×
  • أضف...