البحث في الموقع
المحتوى عن 'svelte'.
-
تعلّمنا في المقال السابق دعم إطار عمل Svelte للغة TypeScript وكيفية استخدام ذلك لجعل تطبيقك أقوى، كما سنتعرّف في هذا المقال على كيفية نشر تطبيقك عبر الإنترنت ومشاركة بعض موارد التعلم التي يجب الانتقال إليها لمواصلة رحلة التعلّم في إطار عمل Svelte. المتطلبات الأساسية: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة باستخدام سطر الأوامر أو الطرفية، وستحتاج طرفية مثبَّت عليها node وnpm لتصريف وبناء تطبيقك. الهدف: التعرّف على كيفية تحضير تطبيق Svelte لعملية الإنتاج والموارد التعليمية التي يجب الاطلاع عليها بعد ذلك. يمكن متابعة كتابة شيفرتك معنا، لذلك انسخ أولًا مستودع github -إذا لم تفعل ذلك مسبقًا- باستخدام الأمر التالي: git clone https://github.com/opensas/mdn-svelte-tutorial.git ثم يمكنك الوصول إلى حالة التطبيق الحالية من خلال تشغيل الأمر التالي: cd mdn-svelte-tutorial/08-next-steps أو يمكنك تنزيل محتوى المجلد مباشرةً كما يلي: npx degit opensas/mdn-svelte-tutorial/08-next-steps تذكَّر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير. تصريف التطبيق شغّلنا حتى الآن تطبيقنا في وضع التطوير باستخدام الأمر npm run dev الذي يخبر إطار عمل Svelte بتجميع المكونات وملفات جافاسكربت في الملف public/build/bundle.js وجميع أقسام CSS الخاصة بالمكونات في الملف public/build/bundle.css، كما يشغّل خادم التطوير ويراقب التغييرات ويعيد تصريف التطبيق وتحديث الصفحة عند حدوث تغيير. سيكون الملفان bundle.js و bundle.css كما يلي (لاحظ وجود حجم الملف على اليسار): 504 Jul 13 02:43 bundle.css 95981 Jul 13 02:43 bundle.js يمكن تصريف التطبيق للإنتاج من خلال تشغيل الأمر npm run build، إذ لن يشغّل إطار عمل Svelte خادم ويب أو يستمر في مراقبة التغييرات في هذه الحالة، ولكن سيصغّر ويضغط ملفات جافاسكربت باستخدام الأداة terser. سيكون الملفان bundle.js و bundle.css كما يلي بعد تشغيل الأمر npm run build: 504 Jul 13 02:43 bundle.css 21782 Jul 13 02:43 bundle.js جرّب تشغيل الأمر npm run build في المجلد الجذر لتطبيقك الآن، كما يمكن أن تتلقى تحذيرًا، لكن يمكنك تجاهله حاليًا. يبلغ حجم تطبيقنا بالكامل الآن 21 كيلوبايت و8.3 كيلوبايت عند ضغطه بتنسيق gzip، كما لا توجد أوقات تشغيل أو اعتماديات إضافية لتنزيلها وتحليلها وتنفيذها والاستمرار في عملها في الذاكرة، إذ حلّل إطار عمل Svelte المكونات وصرّف الشيفرة البرمجية إلى لغة جافاسكربت الصرفة Vanilla JavaScript. عملية التصريف في إطار Svelte سيستخدِم إطار عمل Svelte الأداة rollup بوصفها أداة تحزيم للوحدات افتراضيًا عندما تنشئ تطبيقًا جديدًا باستخدام الأمر الآتي: npx degit sveltejs/template my-svelte-project ملاحظة: يوجد قالب رسمي لاستخدام Webpack والعديد من الإضافات التي يديرها المجتمع لأدوات التحزيم الأخرى. يمكنك أن ترى في الملف package.json أنّ السكربتات dev وstart تستدعي أداة التحزيم Rollup: "scripts": { "build": "rollup -c", "dev": "rollup -c -w", "start": "sirv public" }, نمرّر في السكربت dev الوسيط -w الذي يخبر أداة Rollup بمراقبة الملفات وإعادة البناء عند إجراء التغييرات، فإذا ألقينا نظرةً على الملف rollup.config.js، فيمكننا رؤية أنّ مصرِّف Svelte هو مجرد إضافة من Rollup: import svelte from 'rollup-plugin-svelte'; // … import { terser } from 'rollup-plugin-terser'; const production = !process.env.ROLLUP_WATCH; export default { input: 'src/main.js', output: { sourcemap: true, format: 'iife', name: 'app', file: 'public/build/bundle.js' }, plugins: [ svelte({ // تفعيل عمليات فحص وقت التشغيل عندما لا تكون في وضع الإنتاج dev: !production, // سنستخرج أيّ مكون CSS في ملف منفصل، وهذا أفضل للأداء css: css => { css.write('public/build/bundle.css'); } }), سترى لاحقًا في الملف نفسه كيف تقلل أداة Rollup من حجم السكربتات في وضع الإنتاج وتطلق خادمًا محليًا في وضع التطوير: // استدعِ الأمر "npm run start" في وضع التطوير بمجرد إنشاء الحزمة !production && serve(), // راقب المجلد public وحدّث المتصفح عند حدوث تغييرات عندما لا تكون في وضع الإنتاج !production && livereload('public'), // إذا أردت البناء للإنتاج (أي استخدمت npm run build بدلًا من npm run dev)، فطبّق عملية التصغير production && terser() ], هناك العديد من إضافات Rollup التي تتيح لك تخصيص سلوكها، ومنها الإضافة svelte-preprocess التي يهتم بها فريق Svelte، حيث تعالج هذه الإضافة مسبقًا العديد من اللغات المختلفة في ملفات Svelte مثل PostCSS و SCSS و Less و CoffeeScript و SASS و TypeScript. نشر تطبيق Svelte لا يُعَدّ تطبيق Svelte من وجهة نظر خادم الويب أكثر من مجموعة من ملفات HTML و CSS و JavaScript، فكل ما تحتاجه هو خادم ويب قادر على تقديم ملفات ثابتة أو ساكنة، مما يعني أنه لديك الكثير من الخيارات للاختيار من بينها، إذًا لنلقِ نظرةً على بعض منها. ملاحظة: يمكن تطبيق القسم التالي على أيّ موقع ويب ساكن من جانب العميل يتطلب خطوة بناء، وليس فقط على تطبيقات Svelte. النشر باستخدام Vercel يُعَدّ استخدام Vercel من أسهل الطرق لنشر تطبيق Svelte، ويُعَدّ Vercel منصةً سحابيةً مصمَّمةً خصيصًا للمواقع الساكنة، وتتمتع بدعم معظم أدوات الواجهة الأمامية الشائعة مثل إطار عمل Svelte. اتبع الخطوات التالية لنشر تطبيقك: سجّل للحصول على حساب Vercel. انتقل إلى جذر تطبيقك وشغّل الأمر npx vercel، إذسيُطلَب منك في المرة الأولى إدخال عنوان بريدك الإلكتروني واتباع الخطوات الواردة في البريد الإلكتروني المرسل إلى هذا العنوان لأغراض أمنية. شغّل الأمر npx vercel مرةً أخرى، وسيُطلَب منك الإجابة على بعض الأسئلة كما يلي: > npx vercel Vercel CLI 19.1.2 ? Set up and deploy "./mdn-svelte-tutorial"? [Y/n] y ? Which scope do you want to deploy to? opensas ? Link to existing project? [y/N] n ? What's your project's name? mdn-svelte-tutorial ? In which directory is your code located? ./ Auto-detected Project Settings (Svelte): - Build Command: `npm run build` or `rollup -c` - Output Directory: public - Development Command: sirv public --single --dev --port $PORT ? Want to override the settings? [y/N] n Linked to opensas/mdn-svelte-tutorial (created .vercel) Inspect: https://vercel.com/opensas/mdn-svelte-tutorial/[...] [1s] ✅ Production: https://mdn-svelte-tutorial.vercel.app [copied to clipboard] [19s] Deployed to production. Run `vercel --prod` to overwrite later (https://vercel.link/2F). To change the domain or build command, go to https://zeit.co/opensas/mdn-svelte-tutorial/settings اقبل جميع الإعدادات الافتراضية وسيكون كل شيء على ما يرام. انتقل إلى عنوان "Production" في متصفحك بمجرد الانتهاء من النشر، وسترى تطبيقك منشورًا. كما يمكنك استيراد مشروع Svelte git إلى Vercel من GitHub أو GitLab أو BitBucket. ملاحظة: يمكنك تثبيت Vercel بطريقة عامة باستخدام الأمر npm i -g vercel حتى لا تضطر إلى استخدام npx لتشغيله. النشر التلقائي على صفحات GitLab Pages يمكن استضافة الملفات الساكنة من خلال استخدام خدمات عبر الإنترنت تسمح لك بنشر موقعك تلقائيًا عندما ترفع التغييرات إلى مستودع git، وتتضمن معظم هذه الخدمات إنشاء خط أنابيب نشر يُشغَّل في كل عملية git push، كما تهتم ببناء موقع الويب ونشره. سننشر فيما يلي تطبيق قائمة المهام على GitLab Pages: يجب أولًا التسجيل في GitLab ثم إنشاء مشروع جديد، وسَمّ مشروعك الجديد باسم قصير وسهل مثل mdn-svelte-todo، إذ سيكون لديك عنوان url يشير إلى مستودع GitLab git الجديد مثل git@gitlab.com:[your-user]/[your-project].git ثانيًا، يُفضَّل إضافة ملف .gitignore لإخبار جيت git بالملفات التي يجب استبعادها من عملية التحكم بالمصدر قبل البدءأ برفع المحتوى إلى مستودع git، إذ سنخبر git في حالتنا باستبعاد الملفات الموجودة في المجلد node_modules من خلال إنشاء ملف .gitignore في المجلد الجذر لمشروعك المحلي الذي يحتوي على ما يلي: node_modules/ ثالثًا، لنعد الآن إلى GitLab، إذ سيرحب GitLab بك برسالة تشرح الخيارات المختلفة لرفع ملفاتك الموجودة مسبقًا بعد إنشاء مستودع جديد، لذا اتبع الخطوات المدرجة ضمن عنوان رفع مجلد موجود مسبقًا Push an existing folder: cd your_root_directory # انتقل إلى المجلد الجذر لمشروعك git init git remote add origin https://gitlab.com/[your-user]/mdn-svelte-todo.git git add . git commit -m "Initial commit" git push -u origin main ملاحظة: يمكنك استخدام بروتوكول git بدلًا من بروتوكول https الذي يُعَدّ أسرع ولا يجعلك تكتب اسم المستخدِم وكلمة المرور في كل مرة تدخل فيها إلى مستودعك الأصلي، لكن يجب إنشاء زوج مفاتيح SSH لاستخدامه، وسيكون عنوان URL الأصلي كما يلي: git@gitlab.com:[your-user]/mdn-svelte-todo.git هيّأنا مستودع git محلي عبر التعليمات السابقة، ثم ضبطنا الأصل البعيد حيث سنرفع شيفرتنا ليكون مستودعنا على GitLab، ثم يجب وضع جميع الملفات في مستودع git المحلي وبعد ذلك نرفعها إلى الأصل البعيد على GitLab. يستخدِم GitLab أداةً مبنيةً مسبقًا تسمى GitLab CI/CD لبناء موقعك ونشره على خادم GitLab Pages، إذ يُنشَأ تسلسل السكربتات الذي تشغّله الأداة GitLab CI/CD لإنجاز هذه المهمة من ملف يُدعى .gitlab-ci.yml الذي يمكنك إنشاؤه وتعديله حسب الرغبة، كما ستجعل وظيفة معينة تسمى pages في ملف الإعداد GitLab على دراية بأنك تنشر موقع GitLab Pages. لنجرب ذلك: أولًا، أنشئ الملف .gitlab-ci.yml ضمن جذر مشروعك وضَع فيه المحتوى التالي: image: node:latest pages: stage: deploy script: - npm install - npm run build artifacts: paths: - public only: - main طلبنا من GitLab استخدام صورة مع أحدث إصدار من Node لبناء تطبيقنا، ثم صرّحنا عن الوظيفة pages، لتفعيل GitLab Pages، وكلما كان هناك عملية رفع إلى المستودع، فسيشغّل GitLab الأمرين npm install و npm run build لبناء تطبيقنا، كما نطلب من GitLab نشر محتويات المجلد public، ونُعِد GitLab في السطر الأخير لإعادة نشر تطبيقنا فقط عندما يكون هناك عملية رفع إلى الفرع الرئيسي. ثانيًا، يجب إنشاء مراجع لملفات جافاسكربت وCSS في الملف ذي المسار النسبي public/index.html، بما أنّ تطبيقنا سيُنشَر في مجلد فرعي مثل الآتي: https://your-user.gitlab.io/mdn-svelte-todo ويمكن ذلك من خلال إزالة الشرطات المائلة / من العناوين: /global.css /build/bundle.css /build/bundle.js كما يلي: <title>Svelte To-Do list</title> <link rel='icon' type='image/png' href='favicon.png'> <link rel='stylesheet' href='global.css'> <link rel='stylesheet' href='build/bundle.css'> <script defer src='build/bundle.js'></script> ثالثًا، يجب الآن فقط رفع التغييرات إلى GitLab من خلال تشغيل الأوامر التالية: > git add public/index.html > git add .gitlab-ci.yml > git commit -m "Added .gitlab-ci.yml file and fixed index.html absolute paths" > git push Counting objects: 5, done. Delta compression using up to 8 threads. Compressing objects: 100% (5/5), done. Writing objects: 100% (5/5), 541 bytes | 541.00 KiB/s, done. Total 5 (delta 3), reused 0 (delta 0) To gitlab.com:opensas/mdn-svelte-todo.git 7dac9f3..5725f46 main -> main سيعرض GitLab رمزًا يوضح تقدّم هذه الوظيفة كلما كانت هناك وظيفة تعمل، كما سيسمح لك النقر عليه بفحص الخرج. كما يمكنك التحقق من تقدم الوظائف الحالية والسابقة من قائمة "CI / CD" ثم الخيار "Jobs" الخاص بمشروع GitLab. سيكون تطبيقك متاحًا على https://your-user.gitlab.io/mdn-svelte-todo/ بمجرد انتهاء GitLab من بناء ونشر تطبيقك، ويكون في حالتنا https://opensas.gitlab.io/mdn-svelte-todo/، كما يمكنك التحقق من عنوان URL لصفحتك في واجهة مستخدِم GitLab من قائمة Settings ثم الخيار "Pages". سيُعاد بناء التطبيق تلقائيًا ونشره في GitLab Pages كلما رفعت تغييرات إلى مستودع GitLab باستخدام هذا الإعداد. موارد إضافية لتعلم إطار عمل Svelte سنمنحك الآن بعض الموارد والمشاريع للتحقق منها ومواصلة مشوار تعلّمك إطار عمل Svelte. توثيق Svelte يجب عليك بالتأكيد زيارة الصفحة الرئيسية لإطار Svelte حيث ستجد العديد من المقالات التي تشرحه، فإذا لم تفعل ذلك سابقًا، فاطّلع على برنامج Svelte التعليمي التفاعلي، وقد غطينا فعليًا معظم محتوياته في هذه السلسلة من المقالات، لذا لن يستغرق الأمر وقتًا طويلًا لإكماله، ويمكنك الرجوع إلى توثيق Svelte API والأمثلة المتاحة. مشاريع ذات صلة هناك مشاريع أخرى متعلقة بإطار عمل Svelte تستحق المراجعة ومنها: Sapper: إطار تطبيق مدعوم من Svelte يوفِّر التصيير من جانب الخادم SSR وتقسيم الشيفرة البرمجية والتوجيه المستند إلى الملفات والدعم دون اتصال والمزيد، كما يُعَدّ بمثابة Next.js لإطار عمل Svelte، فإذا أردت تطوير تطبيق ويب معقد إلى حد ما، فيجب عليك بالتأكيد إلقاء نظرة على هذا المشروع. Svelte Native: إطار عمل تطبيقات الهاتف المحمول مدعوم من Svelte، ويُعَدّ بمثابة React Native لإطار عمل Svelte. Svelte for VS Code: إضافة VS Code المدعوم رسميًا للعمل مع ملفات .svelte. الخلاصة تهانينا، فقد أكملت سلسلة مقالات تعلم إطار عمل Svelte، حيث انتقلنا من مرحلة عدم المعرفة بإطار Svelte إلى إنشاء تطبيق كامل ونشره. تعلمنا مفهوم Svelte وما يميزه عن أطر عمل الواجهة الأمامية الأخرى. رأينا كيفية إضافة سلوك ديناميكي إلى موقع الويب، وكيفية تنظيم تطبيقنا في مكونات وطرق مختلفة لمشاركة المعلومات فيما بينها. استفدنا من نظام Svelte التفاعلي وتعلمنا كيفية تجنب الأخطاء الشائعة. رأينا بعض المفاهيم والتقنيات المتقدمة للتفاعل مع عناصر DOM ولتوسيع إمكانيات عناصر HTML برمجيًا باستخدام الموجّه use. رأينا بعد ذلك كيفية استخدام المخازن للعمل مع مستودع بيانات مركزي، وأنشأنا مخزننا المُخصَّص لاستمرار بيانات تطبيقنا في تخزين الويب. كما ألقينا نظرةً على دعم Svelte للغة TypeScript. تعلمنا في هذا المقال خيارين من الخيارات لنشر تطبيقنا في عملية الإنتاج ورأينا كيفية إعداد خط أنابيب أساسي لنشر تطبيقنا على GitLab عند كل تغيير ثم قدمنا قائمةً بموارد Svelte للمضي قدمًا في تعلم Svelte. يجب أن تكون لديك قاعدة قوية يمكنك من خلالها البدء في تطوير تطبيقات ويب احترافية باستخدام إطار عمل Svelte بعد الانتهاء من هذه السلسلة من المقالات. ترجمة -وبتصرُّف- للمقال Deployment and next steps. اقرأ أيضًا تقسيم تطبيق Svelte إلى مكونات إنشاء تطبيق قائمة مهام باستعمال إطار عمل Svelte بدء استخدام إطار العمل Svelte لبناء تطبيقات ويب التفاعلية ودورة الحياة وسهولة وصول المستخدمين في إطار عمل Svelte
-
تعرّفنا في المقال السابق على مخازن 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 بوجود إضافات موصَى بها للتثبيت. سيؤدي النقر على "تثبيت الكل Install all" إلى تثبيت الإضافة Svelte for VS Code. كما يمكننا أن نرى أنّ الملف setupTypeScript.js أجرى بعض التغييرات على مشروعنا، إذ أُعيدت تسمية الملف main.js إلى main.ts، مما يعني أنّ شيفرة VS يمكن أن توفر معلومات التمرير حول مكونات Svelte: سنحصل على ميزة التحقق من الأنواع مجانًا، فإذا مررنا خاصيةً غير معروفة في معامِل الخيارات لدالة البناء App مثل كتابة الخطأ المطبعي traget بدلًا من target، فستعطي TypeScript خطأً كما يلي: أضاف السكربت setupTypeScript.js في المكوِّن App.svelte السمة lang="ts" إلى الوسم <script>. كما لن نحتاج في كثير من الحالات لتحديد الأنواع للحصول على مساعدة الشيفرة البرمجية بفضل ميزة استنتاج الأنواع، فإذا أضفتَ الخاصية ms إلى استدعاء المكوِّن Alert مثلًا، فستستنتج لغة TypeScript من القيمة الافتراضية أنّ الخاصية ms يجب أن تكون عددًا. وإذا مرّرت شيئًا ليس عددًا، فسيظهر خطأ كما يلي: يحتوي قالب التطبيق على سكربت تحقق check مُعَدّ لتشغيل أداة svelte-check للتحقق من شيفرتك البرمجية، إذ تسمح هذه الحزمة باكتشاف الأخطاء والتحذيرات التي يعرضها محرر الشيفرات البرمجية من سطر الأوامر، مما يجعلها مفيدة جدًا لتشغيلها في خط أنابيب تكامل مستمر continuous integration أو CI اختصارًا، لذا شغّل الأمر npm run check لفحص أجزاء شيفرة CSS غير المُستخدَمة، وإعادة تلميحات الشمولية A11y وأخطاء تصريف شيفرة TypeScript. إذا شغّلتَ الأمر 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 لمعرفة كيفية استخدام التحقق من الأنواع الذي حددناه، ولكن سنحصل على الخطأ التالي: يجب الآن أن تحصل على فكرة حول نوع المساعدة التي يمكننا الحصول عليها من لغة TypeScript عند إنشاء مشاريع Svelte، وسنتراجع الآن عن هذه التغييرات من أجل البدء في نقل تطبيقنا إلى لغة TypeScript، لذلك لن نشعر بالانزعاج من جميع تحذيرات التحقق. أزِل المهمة التي تسبب الأخطاء والسمة lang='ts' من الملف Todos.svelte. أزِل استيراد النوع 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. يمكن إصلاح هذا الخطأ من خلال استبدال 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.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 يمكنه قبول الأعداد فقط وسيعطي خطأً إذا حاولنا دفع أيّ شيء آخر: كما يمكن للغة TypeScript استنتاج الأنواع المُعمَّمة من خلال استخدامها، إذ تدعم القيم الافتراضية والقيود، كما تُعَدّ الأنواع المُعمَّمة ميزةً قويةً تسمح للشيفرة البرمجية بتجريد الأنواع المُحدَّدة المُستخدَمة، مما يجعلها أكثر قابليةً لإعادة الاستخدام ومُعمَّمة دون التخلي عن أمان الأنواع. استخدام مخازن Svelte مع الأنواع المعممة تدعم مخازن Svelte الأنواع المُعمَّمة، إذ يمكننا الاستفادة من ميزة استنتاج الأنواع المُعمَّمة دون لمس شيفرتنا البرمجية، فإذا فتحت الملف Todos.svelte وأسندتَ النوع number إلى المخزن $alert، فستحصل على الخطأ التالي: لأنه عندما عرّفنا المخزن 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 بفضل ميزة استنتاج الأنواع المُعمَّمة: كما ذكرنا سابقًا إذا أردنا أن نكون صريحين، فيمكننا ذلك في الملف 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. اقرأ أيضًا بدء استخدام إطار العمل Svelte لبناء تطبيقات ويب مدخل إلى TypeScript الخطوات الأولى في بناء تطبيقات الويب باستعمال TypeScript إنشاء تطبيق قائمة مهام باستعمال إطار عمل Svelte
-
أكملنا في مقال التفاعلية ودورة الحياة وسهولة وصول المستخدمين تطوير تطبيقنا وانتهينا من تنظيمه في مكونات، كما ناقشنا بعض التقنيات المتقدمة للتعامل مع التفاعلية والعمل مع عُقد DOM والوصول إلى وظائف المكونات، وسنعرض في هذا المقال طريقةً أخرى للتعامل مع إدارة الحالة في إطار عمل Svelte وهي المخازن Stores والتي تُعَدّ مستودعات بيانات عامة تحتفظ بالقيم، ويمكن للمكونات الاشتراك بالمخازن وتلقّي إشعارات عندما تتغير قيمها. المتطلبات الأساسية: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة باستخدام سطر الأوامر أو الطرفية، كما ستحتاج طرفية مثبَّت عليها node و npm لتصريف وبناء تطبيقك. الهدف: تعلّم كيفية استخدام المخازن Stores في إطار عمل Svelte. سننشئ المكوِّن 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 على الشاشة، إذ يمكنك النقر عليها لإبعادها. جعل المخازن تفاعلية باستخدام الصيغة التفاعلية $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 من خلال تنفيذ مخزن تعمل ميزاته على النحو التالي: يجب أن يحتوي المخزن على التابع subscribe() الذي يجب أن يقبل دالة اشتراك بوصفها وسيطًا له، ويجب استدعاء جميع دوال الاشتراك النشطة في المخزن عندما تتغير قيمة المخزن. يجب أن تعيد الدالةُ subscribe() الدالةَ unsubscribe() التي يجب أن توقف الاشتراك عند استدعائها. يمكن أن يحتوي المخزن اختياريًا على التابع 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. اقرأ أيضًا التفاعلية ودورة الحياة وسهولة وصول المستخدمين في إطار عمل Svelte بدء استخدام إطار العمل Svelte لبناء تطبيقات ويب إنشاء تطبيق قائمة مهام باستعمال إطار عمل Svelte التعامل مع المتغيرات والخاصيات في إطار عمل Svelte تقسيم تطبيق Svelte إلى مكونات
-
أضفنا في مقال تقسيم تطبيق 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. اقرأ أيضًا بدء استخدام إطار العمل Svelte لبناء تطبيقات ويب إنشاء تطبيق قائمة مهام باستعمال إطار عمل Svelte التعامل مع المتغيرات والخاصيات في إطار عمل Svelte تقسيم تطبيق Svelte إلى مكونات
-
بدأنا في مقال التعامل مع المتغيرات والخاصيات بتطوير تطبيق قائمة المهام، والهدف الأساسي من هذا المقال هو تعلّم كيفية تقسيم تطبيقنا إلى مكونات يمكن إدارتها ومشاركة المعلومات فيما بينها. سنقسّم تطبيقنا إلى مكونات، ثم سنضيف مزيدًا من الوظائف للسماح للمستخدمين بتحديث المكونات الحالية. المتطلبات الأساسية: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة باستخدام سطر الأوامر أو الطرفية، وستحتاج طرفية مثبَّت عليها node و npm لتصريف وبناء تطبيقك. الهدف: تعلم كيفية تقسيم تطبيقنا إلى مكونات ومشاركة المعلومات فيما بينها. يمكنك متابعة كتابة شيفرتك معنا، لذلك انسخ أولًا مستودع github -إذا لم تفعل ذلك مسبقًا- باستخدام الأمر التالي: git clone https://github.com/opensas/mdn-svelte-tutorial.git ثم يمكنك الوصول إلى حالة التطبيق الحالية من خلال تشغيل الأمر التالي: cd mdn-svelte-tutorial/04-componentizing-our-app أو يمكنك تنزيل محتوى المجلد مباشرةً كما يلي: npx degit opensas/mdn-svelte-tutorial/04-componentizing-our-app تذكَّر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير، وإذا أردت متابعتنا فابدأ بكتابة الشيفرة باستخدام الأداة REPL. تقسيم التطبيق إلى مكونات يتكون التطبيق في إطار عمل Svelte من مكوِّن واحد أو من مكونات متعددة، ويُعَدّ المكوِّن كتلةً من الشيفرة البرمجية القابلة لإعادة الاستخدام والمستقلة ذاتيًا والتي تغلّف شيفرة HTML و CSS وجافاسكربت المرتبطة مع بعضها البعض والمكتوبة في ملف .svelte، كما يمكن أن تكون المكونات كبيرةً أو صغيرةً، لكنها تكون عادةً محدَّدةً بوضوح، فالمكونات الأكثر فاعليةً هي المكونات التي تخدم غرضًا واحدًا واضحًا. من فوائد تحديد المكونات هو قابلية هذه المكونات للموازنة مع أفضل الممارسات العامة بهدف تنظيم شيفرتك البرمجية ضمن أجزاء يمكن إدارتها، مما يساعدك على فهم كيفية ارتباطها ببعضها البعض ويعزِّز إعادة الاستخدام ويجعل شيفرتك البرمجية أسهل للتفكير بها وصيانتها وتوسيعها. لا توجد قواعد صارمة لتقسيم المكونات، لذا يفضِّل بعض الأشخاص اتباع نهج بسيط يتمثل بالنظر إلى شيفرة HTML ثم رسم مربعات حول كل مكوِّن ومكوِّن فرعي يبدو أنّ له شيفرته الخاصة، في حين يطبق أشخاص آخرون الأساليب نفسها المُستخدَمة لتحديد ما إذا كان يجب إنشاء دالة أو كائن جديد، وأحد هذه الأساليب هو مبدأ المسؤولية الفردية، أي يجب أن يطبّق المكون شيئًا واحدًا فقط بصورة مثالية، ثم يمكننا تقسيمه إلى مكونات فرعية أصغر إذا لزم الأمر، كما يجب أن يكمل هذا النهجان بعضهما البعض لمساعدتك على تحديد كيفية تنظيم مكوناتك بطريقة أفضل. سنقسم تطبيقنا إلى المكونات التالية: Alert.svelte: مربع إشعارات عام لإرسال الإجراءات التي حدثت. NewTodo.svelte: حقل إدخال النص والزر الذي يسمح بإدخال عنصر مهام جديد. FilterButton.svelte: أزرار "كل المهام All" و"المهام النشطة Active" و"المهام المكتملة Completed" التي تسمح بتطبيق المرشّحات Filters على عناصر المهام المعروضة. TodosStatus.svelte: العنوان الذي يعرض العبارة "x out of y items completed" التي تمثّل عدد المهام المكتملة. Todo.svelte: عنصر مهام مفرد، إذ سيُعرَض كل عنصر مهمة مرئي في نسخة منفصلة من هذا المكوِّن. MoreActions.svelte: الزرّان "تحديد الكل Check All" و"احذف المهام المكتملة Remove Completed" الموجودان أسفل واجهة المستخدِم، ويسمحان بتنفيذ مجموعة إجراءات على عناصر المهام. سنركز في هذا المقال على إنشاء المكونين FilterButton و Todo وسنشرح المكونات الأخرى في المقالات القادمة. ملاحظة: سنتعلم أيضًا في عملية إنشاء أول مكونين تقنيات مختلفة لتواصل المكونات مع بعضها بعضًا، وإيجابيات وسلبيات كل من هذه التقنيات. استخراج مكون الترشيح سننشئ أولًا المكون FilterButton.svelte باتباع الخطوات التالية: أولًا، أنشئ ملفًا جديدًا components/FilterButton.svelte. ثانيًا، سنصرّح عن الخاصية filter في هذا الملف ثم سننسخ شيفرة HTML المتعلقة به من الملف Todos.svelte، لذا أضِف المحتوى التالي إلى هذا الملف: <script> export let filter = 'all' </script> <div class="filters btn-group stack-exception"> <button class="btn toggle-btn" class:btn__primary={filter === 'all'} aria-pressed={filter === 'all'} on:click={()=> 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 === 'active'} aria-pressed={filter === 'active'} on:click={()=> 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 === 'completed'} aria-pressed={filter === 'completed'} on:click={()=> filter = 'completed'} > <span class="visually-hidden">Show</span> <span>Completed</span> <span class="visually-hidden">tasks</span> </button> </div> ثالثًا، ارجع إلى المكوِّن Todos.svelte، حيث نريد الاستفادة من المكوِّن FilterButton، إذ يجب استيراده أولًا، لذا أضِف السطر التالي قبل القسم <script> في المكوِّن Todos.svelte: import FilterButton from './FilterButton.svelte' رابعًا، استبدل الآن العنصر <div> الذي يملك اسم الصنف filters باستدعاء المكون FilterButton الذي يأخذ المرشح الحالي بوصفه خاصيةً كما يلي: <FilterButton {filter} /> ملاحظة: تذكَّر أنه إذا تطابق اسم سمة في لغة HTML مع اسم المتغير، فيمكن استبدالهما بالشكل {variable}، لذا يمكننا استبدال <FilterButton filter={filter} /> بالشكل <FilterButton {filter} />. لنجرب التطبيق الآن، حيث ستلاحظ أنه إذا نقرت على أزرار الترشيح، فستُحدَّد هذه الأزرار وسيُحدَّث التنسيق بطريقة مناسبة، ولكن لدينا مشكلة وهي عدم ترشيح المهام، وسبب هذه المشكلة هو انتقال المتغير filter من المكوِّن Todos إلى المكوِّن FilterButton عبر الخاصية، ولكن لا تنتقل التغييرات التي تحدث في المكوِّن FilterButton مرةً أخرى إلى المكوِّن الأب، إذ يكون ارتباط البيانات أحادي الاتجاه افتراضيًا. مشاركة البيانات بين المكونات: تمرير المعالج بوصفه خاصية تتمثل إحدى طرق السماح للمكونات الأبناء بإعلام المكونات الآباء بأيّ تغييرات في تمرير المعالج بوصفه خاصيةً Prop، حيث سينفّذ المكوِّن الابن المعالج، ويمرّر المعلومات المطلوبة بوصفها معاملًا وسيعدّل المعالج حالة المكوِّن الأب، كما سيتلقّى المكوِّن FilterButton في حالتنا المعالج onclick من المكوِّن الأب، فإذا نقر المستخدِم على أيّ زر ترشيح، فسيستدعي المكونُ الابن المعالجَ onclick ويمرّر المرشّح المحدد على أساس معامل إلى المكوِّن الأب. سنصرّح فقط عن الخاصية onclick التي تُسنَد إلى معالِج وهمي لمنع الأخطاء كما يلي: export let onclick = (clicked) => {} وسنصرّح عن التعليمة التفاعلية $: onclick(filter) لاستدعاء المعالِج onclick كلما جرى تحديث المتغير filter. أولًا، يجب أن يبدو القسم <script> الخاص بالمكوِّن FilterButton كما يلي: <script> export let filter = 'all' export let onclick = (clicked) => {} $: onclick(filter) </script> إذا استدعينا المكوِّن FilterButton ضمن المكوِّن Todos.svelte الآن، فيجب تحديد المعالج، لذا عدّله إلى ما يلي: <FilterButton {filter} onclick={ (clicked) => filter = clicked }/> إذا نقرت على أيّ زر ترشيح، فسنعدّل المتغير filter باستخدام المرشّح الجديد وسيعمل المكوِّن FilterButton مرةً أخرى. طريقة أسهل لربط البيانات ثنائي الاتجاه باستخدام الموجه bind أدركنا في المثال السابق أنّ المكوِّن FilterButton لم يعمل، لأنّ حالة التطبيق تنتقل من المكوِّن الأب إلى المكوِّن الابن من خلال الخاصية filter، ولكنها لا ترجع مرةً أخرى من المكوِّن الابن إلى المكوِّن الأب، لذلك أضفنا الخاصية onclick للسماح للمكوِّن الابن بإرسال قيمة الخاصية filter الجديدة إلى المكوِّن الأب. يعمل التطبيق جيدًا، ولكن يوفر إطار عمل Svelte طريقةً سهلةً ومباشرةً لتحقيق ربط البيانات ثنائي الاتجاه، إذ تتدفق البيانات عادةً من المكوِّن الأب إلى المكوِّن الابن باستخدام الخاصيات، وإذا أردنا أن تتدفق في الاتجاه الآخر من المكوِّن الابن إلى المكوِّن الأب، فيمكننا استخدام الموجّه bind:. سنخبر إطار عمل Svelte باستخدام الموجّه bind أنّ أيّ تغييرات تجرَى على الخاصية filter في المكوِّن FilterButton يجب أن تنتشر إلى المكوِّن الأب Todos، أي أننا سنربط قيمة المتغير filter في المكوِّن الأب بقيمته في المكوِّن الابن. أولًا، عدّل استدعاء المكوِّن FilterButton في Todos.svelte كما يلي: <FilterButton bind:filter={filter} /> يوفِّر إطار عمل Svelte اختصارًا، إذ تعادل التعليمةُ bind:value={value} التعليمةَ bind:value، لذلك يمكنك في المثال السابق كتابة <FilterButton bind:filter /> فقط. ثانيًا، يمكن للمكوِّن الابن الآن تعديل قيمة المتغير filter الخاص بالمكون الأب، لذلك لم نعد بحاجة إلى الخاصية onclick، لذا عدّل القسم <script> الخاص بالمكوِّن FilterButton كما يلي: <script> export let filter = 'all' </script> ثالثًا، جرب تطبيقك مرةً أخرى، وستظل ترى أن المرشّحات تعمل بصورة صحيحة. إنشاء المكون Todo سننشئ الآن المكون Todo لتغليف كل مهمة بما في ذلك مربع الاختيار وشيفرة التعديل لتتمكّن من تعديل مهمة موجودة مسبقًا، وسيتلقى المكوِّن Todo الكائن todo بوصفه خاصيةً، لذا لنصرّح عن الخاصية todo ولننقل الشيفرة البرمجية من المكوِّن Todos، كما سنستبدل حاليًا استدعاء removeTodo باستدعاء alert وسنضيف هذه الوظيفة مرةً أخرى في وقت لاحق. أنشئ ملف مكوِّن جديد components/Todo.svelte، وضَع بعد ذلك المحتويات التالية ضمن هذا الملف: <script> export let todo </script> <div class="stack-small"> <div class="c-cb"> <input type="checkbox" id="todo-{todo.id}" on:click={() => todo.completed = !todo.completed} checked={todo.completed} /> <label for="todo-{todo.id}" class="todo-label">{todo.name}</label> </div> <div class="btn-group"> <button type="button" class="btn"> Edit <span class="visually-hidden">{todo.name}</span> </button> <button type="button" class="btn btn__danger" on:click={() => alert('not implemented')}> Delete <span class="visually-hidden">{todo.name}</span> </button> </div> </div> يجب الآن استيراد المكوِّن Todo إلى Todos.svelte، لذا انتقل إلى هذا الملف الآن وأضف تعليمة الاستيراد import التالية بعد تعليمة الاستيراد الموجودة مسبقًا: import Todo from './Todo.svelte' يجب بعد ذلك تحديث كتلة {#each} لتضمين المكوِّن <Todo> لكل مهمة بدلًا من الشيفرة المنقولة إلى Todo.svelte، ويجب تمرير كائن todo الحالي إلى المكوِّن بوصفه خاصيةً، لذا عدّل كتلة {#each} ضمن المكوِّن Todos.svelte كما يلي: <ul role="list" class="todo-list stack-large" aria-labelledby="list-heading"> {#each filterTodos(filter, todos) as todo (todo.id)} <li class="todo"> <Todo {todo} /> </li> {:else} <li>Nothing to do here!</li> {/each} </ul> تُعرَض قائمة المهام على الصفحة، ويجب أن تعمل مربعات الاختيار (حاول تحديد أو إلغاء تحديد مربعات الاختيار، ثم لاحظ أنّ المرشحات لا تزال تعمل كما هو متوقع)، ولكن لن يُحدَّث عنوان الحالة "x out of y items completed" وفقًا لذلك لأن المكوِّن Todo يتلقى المهام باستخدام الخاصية، لكنه لا يرسل أيّ معلومات إلى المكوِّن الأب، وسنصلح ذلك لاحقًا. مشاركة البيانات بين المكونات: نمط الخاصيات للأسفل Props-down والأحداث للأعلى Events-up يُعَد الموجّه bind واضحًا جدًا ويسمح بمشاركة البيانات بين المكوِّن الأب والمكوِّن الابن، ولكن يمكن أن يكون تتبّع جميع القيم المرتبطة ببعضها بعضًا أمرًا صعبًا عندما ينمو تطبيقك بصورة أكبر وأكثر تعقيدًا، لذا يمكنك استخدام نهج مختلف هو نمط الاتصال "props-down, events-up". يعتمد هذا النمط على المكونات الأبناء التي تتلقى البيانات من آبائها عبر الخاصيات والمكونات الآباء لتحديث حالتها من خلال معالجة الأحداث التي تطلقها المكونات الأبناء، لذا تتدفق الخاصيات للأسفل Flow Down من المكوِّن الأب إلى المكوِّن الابن وتنتشر Bubble Up الأحداث للأعلى من المكوِّن الابن إلى المكوِّن الأب، إذ ينشئ هذا النمط تدفقًا أسهل ثنائي الاتجاه للمعلومات. لنلقِ نظرةً على كيفية إصدار أحداثنا لإعادة تطبيق وظيفة زر "الحذف Delete" المفقودة، إذ يمكن إنشاء أحداث مخصصة من خلال استخدام الأداة createEventDispatcher التي تعيد الدالة dispatch() التي تسمح بإصدار أحداث مخصصة، فإذا أرسلتَ حدثًا، فيجب تمرير اسم الحدث وكائن اختياري به معلومات إضافية تريد تمريرها إلى كل مستمع، كما ستكون هذه البيانات الإضافية متاحةً في الخاصية detail لكائن الحدث. ملاحظة: تشترك الأحداث المخصصة في إطار عمل Svelte بواجهة برمجة التطبيقات نفسها التي تستخدِمها أحداث DOM العادية، كما يمكنك نشر حدث إلى المكوِّن الأب عن طريق تحديد on:event بدونّ أي معالج. سنعدّل المكون Todo لإصدار الحدث remove عبر تمرير المهمة المحذوفة بوصفها معلومات إضافية. أضِف أولًا الأسطر التالية إلى الجزء العلوي من القسم <script> للمكوِّن Todo: import { createEventDispatcher } from 'svelte' const dispatch = createEventDispatcher() عدّل الآن زر "الحذف Delete" في قسم شيفرة HTML بالملف نفسه ليبدو كما يلي: <button type="button" class="btn btn__danger" on:click={() => dispatch('remove', todo)}> Delete <span class="visually-hidden">{todo.name}</span> </button> نصدر الحدث remove من خلال استخدام dispatch('remove', todo) ونمرِّر المهام todo المحذوفة بوصفها بيانات إضافية، إذ سيُستدعى المعالج باستخدام كائن الحدث المتوفر مع البيانات الإضافية المتوفرة في الخاصية event.detail. يجب الآن الاستماع إلى هذا الحدث من داخل الملف Todos.svelte والتصرف وفقًا لذلك، لذا ارجع إلى هذا الملف وعدّل استدعاء المكوِّن <Todo> كما يلي: <Todo {todo} on:remove={e => removeTodo(e.detail)} /> يتلقى معالجنا المعامِل e (كائن الحدث) الذي يحتفظ بالمهام المحذوفة في الخاصية detail. إذا حاولت تجربة تطبيقك مرةً أخرى الآن، فسترى أنّ وظيفة الحذف تعود للعمل، وبذلك نجح حدثنا المخصّص كما توقعنا، كما يرسل مستمع الحدث remove تغيّر البيانات إلى المكوِّن الأب، لذلك سيُحدَّث عنوان الحالة "x out of y items completed" بصورة مناسبة عند حذف المهام. سنهتم الآن بالحدث update بحيث يمكن إعلام المكوِّن الأب بأيّ مهام مُعدَّلة. تحديث المهام لا يزال يتعين علينا تنفيذ الوظيفة للسماح بتعديل المهام الحالية، إذ يجب تضمين وضع التعديل في المكوِّن Todo، كما سنعرض حقل الإدخال <input> عند الدخول في وضع التعديل للسماح بتعديل اسم المهمة الحالي مع زرين لتأكيد التغييرات أو إلغائها. معالجة الأحداث أولًا، سنحتاج متغيرًا واحدًا لتتبّع ما إذا كنا في وضع التعديل أم في وضع آخر لتخزين اسم المهمة المُعدَّلة، لذا أضِف تعريفات المتغيرات التالية في الجزء السفلي من القسم <script> للمكوِّن Todo: let editing = false // تتبّع نمط التعديل let name = todo.name // تخزين اسم المهمة المُعدَّلة يجب أن نقرِّر ما هي الأحداث التي سيصدرها المكوِّن Todo كما يلي: يمكننا إصدار أحداث مختلفة لتبديل الحالة وتعديل الاسم مثل updateTodoStatus و updateTodoName. أو يمكننا اتباع نهج أعم وإصدار حدث update واحد لكلتا العمليتين. سنتخذ النهج الثاني لنتمكن من إظهار طريقة مختلفة، إذ تتمثل ميزة هذا النهج في أنه يمكننا لاحقًا إضافة المزيد من الحقول إلى المهام مع إمكانية معالجة جميع التحديثات باستخدام الحدث نفسه، فلننشئ الدالة update() التي ستتلقى التغييرات وتصدر حدث تحديث مع المهام المُعدَّلة، لذا أضف ما يلي مرةً أخرى إلى الجزء السفلي من القسم <script>: function update(updatedTodo) { todo = { ...todo, ...updatedTodo } // تطبيق تعديلات على المهمة dispatch('update', todo) // إصدار حدث التحديث } استخدمنا صيغة الانتشار Spread Syntax لإعادة المهمة الأصلية مع التعديلات المُطبَّقة عليها. سننشئ بعد ذلك دوالًا مختلفةً للتعامل مع كل إجراء للمستخدِم، إذ يمكن للمستخدِم حفظ التغييرات أو إلغائها عندما تكون المهمة في وضع التعديل، ويمكن للمستخدِم حذف المهمة أو تعديلها أو تبديل حالتها بين الحالة المكتملة والنشطة عندما لا تكون في وضع التعديل، لذا أضف مجموعة الدوال التالية بعد آخر دالة للتعامل مع هذه الإجراءات: function onCancel() { name = todo.name // إعادة المتغير name إلى قيمته الأولية editing = false // والخروج من وضع التعديل } function onSave() { update({ name: name }) // تحديث اسم المهمة editing = false // والخروج من وضع التعديل } function onRemove() { dispatch('remove', todo) // إصدار حدث الحذف } function onEdit() { editing = true // الدخول في وضع التعديل } function onToggle() { update({ completed: !todo.completed}) // تحديث حالة المهمة } تحديث ملف شيفرة HTML يجب الآن تحديث شيفرة HTML الخاصة بالمكون Todo لاستدعاء الدوال السابقة عند اتخاذ الإجراءات المناسبة، إذ يمكنك التعامل مع وضع التعديل من خلال استخدام المتغير editing الذي له قيمة منطقية، فإذا كانت قيمة هذا المتغير true، فيجب أن يُعرَض حقل الإدخال <input> لتعديل اسم المهمة وزرَّي "الإلغاء Cancel" و"الحفظ Save"؛ أما إذا لم تكن في وضع التعديل، فسيُعرَض مربع الاختيار واسم المهمة وأزرار تعديل المهام وحذفها. يمكن تحقيق ذلك من خلال استخدام كتلة if التي تصيّر شيفرة HTML شرطيًا، ولكن ضع في الحسبان أنها لن تظهِر أو تخفي شيفرة HTML بناءً على شرط معيّن، وإنما ستضيف وتزيل عناصر نموذج DOM ديناميكيًا اعتمادًا على هذا الشرط. إذا كانت قيمة المتغير editing هي true مثلًا، فسيعرض إطار عمل Svelte نموذج التحديث؛ أما إذا كانت قيمته false، فسيزيله من نموذج DOM وسيضيف مربع الاختيار، لذا سيكون تعيين قيمة المتغير editing كافيًا لعرض عناصر HTML الصحيحة بفضل خاصية التفاعل في إطار عمل Svelte. ستكون كتلة if كما يلي: <div class="stack-small"> {#if editing} <!-- markup for editing to-do: label, input text, Cancel and Save Button --> {:else} <!-- markup for displaying to-do: checkbox, label, Edit and Delete Button --> {/if} </div> يمثِّل الجزء {:else} أو النصف السفلي من كتلة if قسم عدم التعديل، كما سيكون مشابهًا جدًا للقسم الموجود في المكوِّن Todos، ولكن الاختلاف الوحيد بينهما هو أننا نستدعي الدوال onToggle() و onEdit() و onRemove() اعتمادًا على إجراء المستخدِم. {:else} <div class="c-cb"> <input type="checkbox" id="todo-{todo.id}" on:click={onToggle} checked={todo.completed} > <label for="todo-{todo.id}" class="todo-label">{todo.name}</label> </div> <div class="btn-group"> <button type="button" class="btn" on:click={onEdit}> Edit<span class="visually-hidden"> {todo.name}</span> </button> <button type="button" class="btn btn__danger" on:click={onRemove}> Delete<span class="visually-hidden"> {todo.name}</span> </button> </div> {/if} </div> تجدر الإشارة إلى ما يلي: ننفّذ الدالة onEdit() التي تضبط المتغير editing على القيمة true عندما يضغط المستخدِم على زر "التعديل Edit". نستدعي الدالة onToggle() التي تنفذ الدالة update() من خلال تمرير كائن مع قيمة completed الجديدة بوصفه معامِلًا عندما ينقر المستخدِم على مربع الاختيار. تصدِر الدالة update() الحدث update من خلال تمرير نسخة من المهمة الأصلية مع التغييرات المطبَّقة بوصفها معلومات إضافية. أخيرًا، تصدِر الدالة onRemove() الحدث remove من خلال تمرير المهمة todo المراد حذفها بوصفها بيانات إضافية. ستحتوي واجهة المستخدِم الخاصة بالتعديل -أي النصف العلوي- على حقل الإدخال <input> وزرين لإلغاء التغييرات أو حفظها كما يلي: <div class="stack-small"> {#if editing} <form on:submit|preventDefault={onSave} class="stack-small" on:keydown={e => e.key === 'Escape' && onCancel()}> <div class="form-group"> <label for="todo-{todo.id}" class="todo-label">New name for '{todo.name}'</label> <input bind:value={name} type="text" id="todo-{todo.id}" autoComplete="off" class="todo-text" /> </div> <div class="btn-group"> <button class="btn todo-cancel" on:click={onCancel} type="button"> Cancel<span class="visually-hidden">renaming {todo.name}</span> </button> <button class="btn btn__primary todo-edit" type="submit" disabled={!name}> Save<span class="visually-hidden">new name for {todo.name}</span> </button> </div> </form> {:else} [...] إذا ضغط المستخدِم على زر "التعديل Edit"، فسيُضبَط المتغير editing على القيمة true وسيزيل إطار عمل Svelte شيفرة HTML في الجزء {:else} من نموذج DOM وسيستبدله بشيفرة HTML الموجودة في القسم {#if}. ستكون الخاصية value الخاصة بالعنصر <input> مرتبطةً بالمتغير name، وستستدعي أزرار إلغاء التغييرات وحفظها الدالتين onCancel() و onSave() على التوالي كما يلي، وقد أضفنا هاتين الدالتين سابقًا: إذا استُدعيت الدالة onCancel()، فستُعاد الخاصية name إلى قيمتها الأصلية عند تمريرها بوصفها خاصيةً Prop وسنخرج من وضع التعديل عن طريق ضبط المتغير editing على القيمة false. إذا استُدعيت الدالة onSave()، فسنشغّل الدالة update() من خلال تمرير الخاصية name المُعدَّلة، وسنخرج من وضع التعديل. كما نعطّل زر "الحفظ Save" عندما يكون حقل الإدخال <input> فارغًا باستخدام السمة disabled={!name}، كما نسمح للمستخدِم بإلغاء التعديل باستخدام المفتاح Escape كما يلي: on:keydown={e => e.key === 'Escape' && onCancel()}. كما نستخدِم الخاصية todo.id لإنشاء معرّفات فريدة لعناصر التحكم بحقل الإدخال والتسميات Labels الجديدة. تبدو شيفرة HTML المعدَّلة الكاملة للمكون Todo كما يلي: <div class="stack-small"> {#if editing} <!-- markup for editing todo: label, input text, Cancel and Save Button --> <form on:submit|preventDefault={onSave} class="stack-small" on:keydown={e => e.key === 'Escape' && onCancel()}> <div class="form-group"> <label for="todo-{todo.id}" class="todo-label">New name for '{todo.name}'</label> <input bind:value={name} type="text" id="todo-{todo.id}" autoComplete="off" class="todo-text" /> </div> <div class="btn-group"> <button class="btn todo-cancel" on:click={onCancel} type="button"> Cancel<span class="visually-hidden">renaming {todo.name}</span> </button> <button class="btn btn__primary todo-edit" type="submit" disabled={!name}> Save<span class="visually-hidden">new name for {todo.name}</span> </button> </div> </form> {:else} <!-- markup for displaying todo: checkbox, label, Edit and Delete Button --> <div class="c-cb"> <input type="checkbox" id="todo-{todo.id}" on:click={onToggle} checked={todo.completed} > <label for="todo-{todo.id}" class="todo-label">{todo.name}</label> </div> <div class="btn-group"> <button type="button" class="btn" on:click={onEdit}> Edit<span class="visually-hidden"> {todo.name}</span> </button> <button type="button" class="btn btn__danger" on:click={onRemove}> Delete<span class="visually-hidden"> {todo.name}</span> </button> </div> {/if} </div> ملاحظة: يمكننا أيضًا تقسيم هذا المكوِّن إلى مكوِّنين مختلفين أحدهما لتعديل المهام والآخر لعرضها، إذ يتلخص الأمر في مدى شعورك بالراحة في التعامل مع هذا المستوى من التعقيد باستخدام مكوِّن واحد، لذا يجب التفكير فيما إذا كان تقسيمه سيمكّنك أكثر من إعادة استخدام هذا المكوِّن في سياق مختلف. يجب معالجة الحدث update من المكوِّن Todos لتشغيل وظيفة التحديث، لذا أضف معالِج الأحداث التالي في القسم <script>: function updateTodo(todo) { const i = todos.findIndex(t => t.id === todo.id) todos[i] = { ...todos[i], ...todo } } نجد المهمة todo باستخدام معرِّفها id في مصفوفة المهام todos ونحدّث محتواها باستخدام صيغة الانتشار، وقد كان بإمكاننا أيضًا استخدام todos[i] = todo في هذه الحالة، ولكن هذا التطبيق أفضل، مما يسمح للمكوِّن Todo بإعادة الأجزاء المُعدَّلة فقط من المهام. يجب بعد ذلك الاستماع إلى الحدث update في استدعاء المكون <Todo>، وتشغيل الدالة updateTodo() عند حدوث ذلك لتغيير المتغير name والحالة completed، لذا عدّل استدعاء المكوِّن <Todo> كما يلي: {#each filterTodos(filter, todos) as todo (todo.id)} <li class="todo"> <Todo {todo} on:update={e => updateTodo(e.detail)} on:remove={e => removeTodo(e.detail)} /> </li> جرب تطبيقك مرةً أخرى وسترى أنه يمكنك حذف وإضافة وتعديل وإلغاء تعديل وتبديل حالة اكتمال المهام، وسيُعدَّل عنوان الحالة "x out of y items completed" بطريقة مناسبة عند اكتمال المهام. يُعَدّ تطبيق نمط "props-down, events-up" في إطار عمل Svelte سهلًا، ولكن يمكن أن يكون الموجّه bind اختيارًا جيدًا للمكونات البسيطة، وسيتيح لك إطار Svelte الاختيار. ملاحظة: يوفِّر إطار Svelte آليات أكثر تقدمًا لمشاركة المعلومات بين المكونات، وهي واجهة Context API والمخازن Stores، إذ توفِّر Context API آليةً للمكونات وأحفادها للتواصل مع بعضها البعض دون تمرير البيانات والدوال بوصفها خاصيات، أو إرسال الكثير من الأحداث، في حين تتيح المخازن Stores مشاركة البيانات التفاعلية بين المكونات غير المرتبطة بطريقة هرمية. يمكنك الوصول إلى نسختك من مستودعنا على النحو التالي لمعرفة حالة الشيفرة كما يجب أن تكون في نهاية هذا المقال: cd mdn-svelte-tutorial/05-advanced-concepts أو يمكنك تنزيل محتوى المجلد مباشرةً باستخدام الأمر التالي: npx degit opensas/mdn-svelte-tutorial/05-advanced-concepts تذكر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير، فإذا أردت متابعتنا فابدأ بكتابة الشيفرة باستخدام الأداة REPL. الخلاصة أضفنا جميع الوظائف المطلوبة لتطبيقنا، إذ يمكننا عرض المهام وإضافتها وتعديلها وحذفها وتمييزها على أنها مكتملة وترشيحها حسب الحالة، كما غطينا في هذا المقال المواضيع التالية: استخراج وظائف مكوِّن جديد. تمرير المعلومات من المكوِّن الابن إلى المكوِّن الأب باستخدام معالج يُستقبَل بوصفه خاصيةً. تمرير المعلومات من المكوِّن الابن إلى المكوِّن الأب باستخدام الموجّه bind. عرض كتل شيفرة HTML المشروطة باستخدام كتلة if. تطبيق نمط الاتصال "props-down, events-up". إنشاء الأحداث المخصصة والاستماع إليها. سنواصل في المقال التالي من جزئية svelte من هذه السلسلة تقسيم تطبيقنا إلى مكونات ونتعرف على بعض التقنيات المتقدمة للعمل مع نموذج DOM. ترجمة -وبتصرُّف- للمقال Componentizing our Svelte app. اقرأ أيضًا التعامل مع المتغيرات والخاصيات في إطار عمل Svelte إنشاء تطبيق قائمة مهام باستعمال إطار عمل Svelte بدء استخدام إطار العمل Svelte لبناء تطبيقات ويب
-
يمكننا الآن البدء في تطوير الميزات المطلوبة لتطبيق قائمة المهام في إطار عمل Svelte بعد أن أصبح التوصيف والتنسيق جاهزًا، إذ سنستخدِم في هذا المقال المتغيرات والخاصيات Props لجعل تطبيقنا ديناميكيًا، مما يسمح بإضافة وحذف المهام ووضع علامة على المهام المكتملة وترشيحها حسب الحالة. المتطلبات الأساسية: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة باستخدام سطر الأوامر أو الطرفية، وستحتاج طرفية مثبَّت عليها node وnpm لتصريف وبناء تطبيقك. الهدف: تعلّم وتطبيق بعض مفاهيم Svelte الأساسية مثل إنشاء المكونات وتمرير البيانات باستخدام الخاصيات وتصيير Render تعابير جافاسكربت في شيفرة HTML وتعديل حالة المكونات وتكرارها عبر القوائم. يمكن متابعة كتابة شيفرتك معنا، لذلك انسخ أولًا مستودع github -إذا لم تفعل ذلك مسبقًا- باستخدام الأمر التالي: git clone https://github.com/opensas/mdn-svelte-tutorial.git ثم يمكنك الوصول إلى حالة التطبيق الحالية من خلال تشغيل الأمر التالي: cd mdn-svelte-tutorial/03-adding-dynamic-behavior أو يمكنك تنزيل محتوى المجلد مباشرةً كما يلي: npx degit opensas/mdn-svelte-tutorial/03-adding-dynamic-behavior تذكَّر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير، وإذا أردت متابعتنا فابدأ بكتابة الشيفرة باستخدام الأداة REPL من هنا. التعامل مع المهام يعرِض المكوِّن Todos.svelte حاليًا شيفرة HTML ثابتةً لا تتغير، إذًا لنبدأ في جعله أكثر ديناميكيةً، إذ سنأخذ معلومات المهام من شيفرة HTML ونخزنها في المصفوفة todos، كما سننشئ متغيرين لتتبّع العدد الإجمالي للمهام والمهام المكتملة، إذ ستُمثَّل حالة المكوِّن من خلال هذه المتغيرات الثلاثة ذات المستوى الأعلى. أولًا، أنشئ قسم <script> قبل المكوِّن src/components/Todos.svelte وضَع فيه المحتوى التالي: <script> let 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 } ]; let totalTodos = todos.length; let completedTodos = todos.filter((todo) => todo.completed).length; </script> لنبدأ بعد ذلك بإظهار رسالة الحالة، لذا ابحث عن العنوان <h2> الذي له المعرِّف id بالقيمة list-heading واستبدل العدد الثابت للمهام النشطة والمكتملة بتعابير ديناميكية كما يلي: <h2 id="list-heading">{completedTodos} out of {totalTodos} items completed</h2> انتقل إلى التطبيق وسترى الرسالة "2 out of 3 items completed" كما كانت سابقًا، ولكن تأتي المعلومات هذه المرة من المصفوفة todos. أخيرًا، يمكن إثبات ذلك من خلال الانتقال إلى تلك المصفوفة ثم محاولة تغيير بعض قيم الخاصية المكتملة completed لكائن المهمة، ويمكنك إضافة كائن مهمة جديد أيضًا، ولاحظ كيف تُحدَّث الأعداد في الرسالة بطريقة مناسبة. إنشاء مهام من بيانات يدخلها المستخدم تُعَدّ عناصر المهام المعروضة ثابتةً حاليًا، ونريد تكرار كل عنصر في المصفوفة todos وتصيير Render شيفرة HTML لكل مهمة. ليس لدى لغة HTML طريقة للتعبير عن المنطق مثل التعابير الشرطية والحلقات، ولكن إطار عمل Svelte يمكنه ذلك من خلال استخدام الموجّه {#each...} للتكرار عبر المصفوفة todos. يتضمن المعامِل الثاني -إذا كان موجودًا- فهرس العنصر الحالي، كما يمكن توفير تعبير مفتاحي يحدد كل عنصر بطريقة فريدة، وسيستخدِمه إطار Svelte لمعرفة الاختلاف في القائمة عند تغيير البيانات بدلًا من إضافة العناصر أو إزالتها في النهاية، ويُعَدّ تحديد عنصر مفتاحي دائمًا من الممارسات الجيدة، كما يمكن توفير كتلة :else التي ستُصيَّر عندما تكون القائمة فارغةً. أولًا، استبدل العنصر <ul> الحالي بالإصدار المبسط التالي لتفهم كيفية العمل: <ul> {#each todos as todo, index (todo.id)} <li> <input type="checkbox" checked={todo.completed}/> {index}. {todo.name} (id: {todo.id}) </li> {:else} Nothing to do here! {/each} </ul> ثانيًا، ارجع إلى التطبيق وسترى شيئًا يشبه ما يلي: رأينا الآن أنّّ كل شيء يعمل جيدًا، فلننشئ عنصر مهمة مكتملة مع كل حلقة للموجّه {#each}، ونضمّن فيها المعلومات من المصفوفة todos مثل id وname وcompleted واستبدل كتلة <ul> الحالية بما يلي: <!-- Todos --> <ul role="list" class="todo-list stack-large" aria-labelledby="list-heading"> {#each todos as todo (todo.id)} <li class="todo"> <div class="stack-small"> <div class="c-cb"> <input type="checkbox" id="todo-{todo.id}" checked={todo.completed}/> <label for="todo-{todo.id}" class="todo-label"> {todo.name} </label> </div> <div class="btn-group"> <button type="button" class="btn"> Edit <span class="visually-hidden">{todo.name}</span> </button> <button type="button" class="btn btn__danger"> Delete <span class="visually-hidden">{todo.name}</span> </button> </div> </div> </li> {:else} <li>Nothing to do here!</li> {/each} </ul> لاحظ كيفية استخدام الأقواس المعقوصة لتضمين تعابير جافاسكربت ضمن سمات HTML كما فعلنا مع السمتين checked و id لمربع الاختيار. حوّلنا بذلك شيفرة HTML الثابتة إلى قالب ديناميكي جاهز لعرض المهام من حالة المكوِّن. التعامل مع الخاصيات لا يُعَدّ المكوِّن Todos مفيدًا جدًا بوجود قائمة مهام ثابتة، إذ يمكن تحويل المكون إلى محرّر مهام للأغراض العامة من خلال السماح لأب هذا المكون بالمرور على قائمة المهام لتعديلها، مما يسمح بحفظها في خدمة ويب أو في التخزين المحلي واستعادتها لاحقًا لتحديثها، لذلك لنحوّل المصفوفة إلى خاصية prop. أولًا، استبدل كتلة let todos = ... الموجودة مسبقًا في Todos.svelte بالتعليمة التالية: export let todos = [] يمكن أن يبدو هذا غريبًا بعض الشيء في البداية، فهذه ليست الطريقة التي تعمل بها تعليمة export في وحدات جافاسكربت، وإنما هي الطريقة التي يوسّع بها إطار عمل Svelte شيفرة جافاسكربت من خلال استخدام صيغة صالحة لهدف جديد. يستخدِم إطار عمل Svelte في حالتنا الكلمة export لتمييز التصريح عن متغير بوصفه خاصية Property أو Prop، مما يعني أنه يصبح في متناول مستخدِمي المكوِّن، كما يمكنك تحديد قيمة أولية افتراضية للخاصية التي تُستخدَم إذا لم يحدد مستخدِم المكون الخاصية الخاصة بالمكوِّن أو إذا كانت قيمتها الأولية غير محدَّدة عند إنشاء نسخة من المكوِّن، لذا نخبر إطار Svelte من خلال التعليمة export let todos = [] أنّ المكوِّن Todos.svelte سيقبل السمة todos والتي ستُهيَّأ إلى مصفوفة فارغة عند حذفها. ثانيًا، ألقِ نظرةً على التطبيق وسترى الرسالة "Nothing to do here!" لأننا لا نمرّر حاليًا أيّ قيمة إليه من المكوِّن App.svelte، لذلك سيستخدِم القيمة الافتراضية. ثالثًا، لننقل مصفوفة مهامنا todos إلى المكوِّن App.svelte ولنمرّرها إلى المكوِّن Todos.svelte بوصفها خاصيةً، لذا عدِّل المكوِّن src/App.svelte كما يلي: <script> import Todos from "./components/Todos.svelte"; let 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> <Todos todos={todos} /> أخيرًا، يسمح إطار عمل Svelte بتحديد المتغير بوصفه اختصارًا عندما يكون للسمة والمتغير الاسم نفسه، ويمكننا بذلك إعادة كتابة السطر الأخير كما يلي: <Todos {todos} /> يجب أن تُصيَّر المهام كما كانت سابقًا باستثناء أننا نمرّرها الآن من المكوِّن App.svelte. تبديل وإزالة المهام سنضيف الآن بعض الوظائف لتبديل حالة المهمة، إذ يحتوي إطار Svelte على الموجّه on:eventname للاستماع إلى أحداث DOM، لذا أضِف معالجًا إلى الحدث on:click الخاص بمربع الاختيار لتبديل القيمة المكتملة. أولًا، عدّل العنصر <input type="checkbox"> في المكوِّن src/components/Todos.svelte كما يلي: <input type="checkbox" id="todo-{todo.id}" on:click={() => todo.completed = !todo.completed} checked={todo.completed} /> ثانيًا، سنضيف دالة لإزالة مهمة من المصفوفة todos، لذا أضف الدالة removeTodo() في الجزء السفلي من القسم <script> في المكوِّن Todos.svelte كما يلي: function removeTodo(todo) { todos = todos.filter((t) => t.id !== todo.id) } سنستدعي بعد ذلك هذه الدالة باستخدام زر "الحذف Delete"، لذا عدّلها باستخدام الحدث click كما يلي: <button type="button" class="btn btn__danger" on:click={() => removeTodo(todo)} > Delete <span class="visually-hidden">{todo.name}</span> </button> يُعَدّ تمرير نتيجة تنفيذ دالة بوصفها معالجًا بدلًا من تمرير الدالة من الأخطاء الشائعة جدًا في المعالجات في إطار Svelte، فإذا حدّدت on:click={removeTodo(todo)} مثلًا، فستُنفَّذ الدالة removeTodo(todo) وستُمرَّر النتيجة بوصفها معالِجًا، لذا يجب تحديد on:click={() => removeTodo(todo)} على أساس معالج، فإذا لم تأخذ الدالة removeTodo() أيّ معامِل، فيمكنك استخدام on:event={removeTodo}، ولكن لا يمكنك استخدام on:event={removeTodo()}، كما أنّ ليس هذا الشكل صيغةً خاصةً من Svelte، وإنما استخدمنا دوال جافاسكربت السهمية Arrow Functions العادية. يمكننا الآن حذف المهام، إذ تُزال المهام ذات الصلة من المصفوفة todos عند الضغط على زر حذف عنصر المهمة، وتُحدَّث واجهة المستخدِم لعدم إظهاره لاحقًا، كما يمكننا الآن تحديد مربعات الاختيار، وستُحدَّث الحالة المكتملة للمهام ذات الصلة في المصفوفة todos، لكن لا يُحدَّث العنوان "x out of y items completed". المهام التفاعلية يعرِف إطار Svelte كيفية تحديث واجهة المستخدِم في كل مرة تُعدَّل فيها قيمة متغير المستوى الأعلى للمكوِّن، حيث تُعدَّل في تطبيقنا قيمة المصفوفة todos مباشرةً في كل مرة تُبدَّل أو تُحذَف فيها المهام المطلوبة، وبالتالي سيحدّث إطار Svelte نموذج DOM تلقائيًا. لا ينطبق الأمر نفسه على المتغيرين totalTodos وcompletedTodos، إذ يُسنَد إليهما قيمة عند إنشاء نسخة من المكوِّن وينفَّذ السكربت في الشيفرة التالية، ولكن لا تُعدَّل قيمتهما بعد ذلك: let totalTodos = todos.length let completedTodos = todos.filter((todo) => todo.completed).length يمكننا إعادة حسابهما بعد تبديل المهام وإزالتها، ولكن هناك طريقة أسهل لذلك، حيث نخبر إطار Svelte بأننا نريد أن يكون المتغيران totalTodos و completedTodos تفاعليين من خلال جعلهما مسبوقين بالرمز :$، إذ سينشئ إطار Svelte الشيفرة لتحديثهما تلقائيًا كلما تغيرت البيانات التي يعتمدان عليها. ملاحظة: يستخدِم إطار Svelte صيغة تعليمة تسمية جافاسكربت :$ لتمييز التعليمات التفاعلية مثل الكلمة export المستخدَمة للتصريح عن الخاصيات، إذ يُعَد هذا المثال مثالًا آخرًا يستفيد فيه إطار Svelte من صيغة جافاسكربت صالحة مع إعطائها هدفًا جديدًا، وهو في هذه الحالة "إعادة تشغيل هذه الشيفرة كلما تغيرت أيّ من القيم المشار إليها". عدِّل تعريف المتغيرين totalTodos وcompletedTodos ضمن الملف src/components/Todos.svelte لتبدو كما يلي: $: totalTodos = todos.length $: completedTodos = todos.filter((todo) => todo.completed).length إذا فحصت تطبيقك الآن، فسترى تحديث أرقام العناوين عند اكتمال المهام أو حذفها. يحلّل مصرِّف Svelte الشيفرة لإنشاء شجرة اعتماديات، ثم ينشئ شيفرة جافاسكربت لإعادة تقييم كل تعليمة تفاعلية كلما حُدِّثت إحدى اعتمادياتها، كما تُطبَّق التفاعلية في Svelte بطريقة خفيفة الوزن وفعالة دون استخدام المستمعِين Listeners أو التوابع الجالبة Getters أو الضابطة Setters أو أيّ آلية معقدة أخرى. إضافة مهام جديدة يجب الآن إضافة بعض الوظائف لإضافة مهام جديدة. أولًا، سننشئ متغيرًا للاحتفاظ بنص المهام الجديدة، لذا أضف التصريح التالي إلى القسم <script> في الملف Todos.svelte: let newTodoName = '' سنستخدِم الآن هذه القيمة في العنصر <input> لإضافة مهام جديدة، وسنحتاج ربط المتغير newTodoName بدخل todo-0، بحيث تبقى قيمة المتغير newTodoName متزامنةً مع الخاصية value الخاصة بالعنصر <input> كما يلي: <input value={newTodoName} on:keydown={(e) => newTodoName = e.target.value} /> كلما تغيرت قيمة المتغير newTodoName، فسينتقل هذا التغيير إلى السمة value الخاصة بحقل الإدخال، وكلما ضُغِط على مفتاح في حقل الإدخال، فسنحدّث محتويات المتغير newTodoName، إذ يُعَدّ ذلك تطبيقًا يدويًا لربط البيانات ثنائي الاتجاه لحقل الإدخال، لكننا لسنا بحاجة لهذه الآلية، إذ يوفِّر إطار Svelte طريقةً أسهل لربط أيّ خاصية بمتغير باستخدام الموجّه bind:property كما يلي: <input bind:value={newTodoName} /> إذًا لنعدّل حقل الإدخال todo-0 كما يلي: <input bind:value={newTodoName} type="text" id="todo-0" autocomplete="off" class="input input__lg" /> يمكن اختبار نجاح هذه الطريقة من خلال إضافة تعليمة تفاعلية لتسجيل محتويات المتغير newTodoName، لذا أضف مقتطف الشيفرة التالي في نهاية القسم <script>: $: console.log('newTodoName: ', newTodoName) ملاحظة: لا تقتصر التعليمات التفاعلية على التصريح عن المتغيرات، إذ يمكنك وضع أيّ تعليمة جافاسكربت بعد الرمز :$. ارجع الآن إلى المضيف المحلي localhost:5042 واضغط على الاختصار Ctrl + Shift + K لفتح طرفية المتصفح واكتب شيئًا ما في حقل الإدخال، ويجب أن ترى إدخالاتك مسجلةً، كما يمكنك الآن حذف التابع console.log() التفاعلي إذا رغبت في ذلك. سننشئ بعد ذلك دالةً لإضافة مهمة جديدة وهي الدالة addTodo() التي ستدفع كائن todo جديد إلى المصفوفة todos، لذا أضف ما يلي إلى الجزء السفلي من كتلة <script> ضمن الملف src/components/Todos.svelte: function addTodo() { todos.push({ id: 999, name: newTodoName, completed: false }) newTodoName = '' } ملاحظة: سنسنِد حاليًا المعرِّف id نفسه لكل مهمة، ولكن لا تقلق إذ سنصلح ذلك لاحقًا. نريد الآن تحديث ملف HTML لاستدعاء الدالة addTodo() كلما أُرسِل النموذج، لذا عدّل وسم فتح النموذج NewTodo كما يلي: <form on:submit|preventDefault={addTodo}> يدعم الموجّه on:eventname إضافة مُعدِّلات إلى حدث DOM باستخدام المحرف |، حيث يخبر المُعدِّل preventDefault إطار Svelte بإنشاء شيفرة لاستدعاء التابع event.preventDefault() قبل تشغيل المعالج. إذا حاولت إضافة مهام جديدة، فستُضاف هذه المهام الجديدة إلى المصفوفة todos، ولكن لن تُحدَّث واجهة المستخدِم، وتذكَّر أنه في إطار Svelte يبدأ التفاعل باستخدام الإسنادات، وهذا يعني تنفيذ الدالة addTodo() وإضافة عنصر إلى المصفوفة todos، ولكن لن يكتشف إطار Svelte أن تابع الدفع قد عدّل المصفوفة، وبذلك لن يحدّث مهام العنصر <ul>، كما ستؤدي إضافة todos = todos إلى نهاية الدالة addTodo() إلى حل هذه المشكلة، ولكن يبدو تضمين ذلك في نهاية الدالة أمرًا غريبًا، لذلك سنأخذ التابع push() مع استخدام صيغة الانتشار Spread Syntax لتحقيق النتيجة نفسها، إذ سنسند قيمة إلى المصفوفة todos تساوي المصفوفة todos بالإضافة إلى الكائن الجديد. ملاحظة: المصفوفة Array لديها العديد من العمليات المتغيرة مثل push() و pop() و splice() و shift() و unshift() و reverse() و sort() التي يمكن أن يتسبب استخدامها في حدوث آثار جانبية وأخطاء يصعب تتبعها، لذا نتجنب تغيّر المصفوفة باستخدام صيغة الانتشار بدلًا من التابع push()، ويُعَدّ ذلك من الممارسات جيدة. عدّل الدالة addTodo() كما يلي: function addTodo() { todos = [...todos, { id: 999, name: newTodoName, completed: false }] newTodoName = '' } إعطاء كل مهمة معرفا فريدا إذا حاولت إضافة مهام جديدة في تطبيقك الآن، فستتمكن من إضافة مهام جديدة وستظهر في واجهة المستخدِم أيضًا، ولكن إذا جربته مرةً ثانية، فلن يعمل، وستتلقى رسالة تقول "Error: Cannot have duplicate keys in a keyed each"، أي نحتاج إلى معرِّفات فريدة لمهامنا. لنصرّح أولًا عن المتغير newTodoId يُحسَب من عدد المهام زائد 1، ولنجعله تفاعليًا، لذا أضِف مقتطف الشيفرة التالي إلى القسم <script>: let newTodoId $: { if (totalTodos === 0) { newTodoId = 1; } else { newTodoId = Math.max(...todos.map((t) => t.id)) + 1; } } ملاحظة: لا تقتصر التعليمات التفاعلية على سطر واحد One-liners، كما يمكن استخدام التعليمة التفاعلية الآتية: $: newTodoId = totalTodos ? Math.max(...todos.map(t => t.id)) + 1 : 1 والتي تُعَدّ مفيدةً أيضًا، ولكنه أقل قابليةً للقراءة. يحلّل المصرِّف التعليمة التفاعلية بأكملها، ويكتشف أنها تعتمد على المتغير totalTodos والمصفوفة todos. لذا كلما عُدِّل أيّ منهما، فسيُعاد تقييم هذه الشيفرة وتحديث newTodoId وفقًا لذلك، ولنستخدِم ذلك في الدالة addTodo()، ولنعدّلها كما يلي: function addTodo() { todos = [...todos, { id: newTodoId, name: newTodoName, completed: false }] newTodoName = '' } ترشيح المهام حسب الحالة لنطبّق الآن القدرة على ترشيح مهامنا حسب الحالة، لذا سننشئ متغيرًا للاحتفاظ بالمرشِّح الحالي، ودالة مساعدة ستعيد المهام المُرشَّحة. أولًا، أضف ما يلي في الجزء السفلي من القسم <script>: let filter = 'all' const filterTodos = (filter, todos) => filter === 'active' ? todos.filter((t) => !t.completed) : filter === 'completed' ? todos.filter((t) => t.completed) : todos نستخدِم المتغير filter للتحكم في مرشّح جميع المهام all أو المهام النشطة active أو المكتملة completed، إذ سيؤدي إسناد إحدى هذه القيم إلى المتغير filter إلى تفعيل المرشح وتحديث قائمة المهام، إذ ستتلقى الدالة filterTodos() المرشِّح الحالي وقائمة المهام وستعيد مصفوفةً جديدةً من المهام المُرشَّحة وفقًا لذلك. لنحدّث بعد ذلك شيفرة HTML الخاصة بزر الترشيح لجعله ديناميكيًا ولنحدّث المرشِّح الحالي عندما يضغط المستخدِّم على أحد أزرار الترشيح كما يلي: <div class="filters btn-group stack-exception"> <button class="btn toggle-btn" class:btn__primary={filter === 'all'} aria-pressed={filter === 'all'} on:click={()=> 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 === 'active'} aria-pressed={filter === 'active'} on:click={()=> 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 === 'completed'} aria-pressed={filter === 'completed'} on:click={()=> filter = 'completed'} > <span class="visually-hidden">Show</span> <span>Completed</span> <span class="visually-hidden">tasks</span> </button> </div> سنعرِض المرشِّح الحالي من خلال تطبيق الصنف btn__primary على زر ترشيح المهام النشطة، ويمكنك تطبيق أصناف تنسيق CSS شرطيًا على عنصر من خلال استخدام الموجِّه class:name={value}، فإذا قُيِّمت عبارة القيمة على أنها صحيحة، فسيُطبَّق اسم الصنف، كما يمكنك إضافة العديد من هذه الموجّهات بشروط مختلفة إلى العنصر نفسه، لذلك إذا كانت التعليمة class:btn__primary={filter === 'all'}، فسيطبّق إطار Svelte الصنف btn__primary إذا كان المرشح يساوي جميع المهام all. ملاحظة: يوفِّر إطار العمل Svelte اختصارًا يتيح لنا إمكانية اختصار <div class:active={active}> إلى <div class:active> عندما يتطابق الصنف Class مع اسم المتغير. يحدث شيء مشابه مع aria-pressed={filter === 'all'} عند تقييم تعبير جافاسكربت الممرَّر بين الأقواس المعقوصة إلى قيمة صحيحة، حيث ستُضاف السمة aria-pressed إلى الزر، وبالتالي سنحدِّث متغير filter باستخدام class:btn__primary={filter === 'all'} كلما نقرنا على الزر. يجب الآن استخدام الدالة المساعدة في حلقة {#each} كما يلي: ... <ul role="list" class="todo-list stack-large" aria-labelledby="list-heading"> {#each filterTodos(filter, todos) as todo (todo.id)} ... يكتشف إطار Svelte بعد تحليل شيفرتنا أنّ الدالة filterTodos() تعتمد على المتغيرين filter و todos، وكلما تغيرت أيّ من هذه الاعتماديات، فسيُحدَّث نموذج DOM وفقًا لذلك، لذا كلما تغيَّر المتغيران filter وtodos، فسيُعاد تقييم الدالة filterTodos() وستُحدَّث العناصر الموجودة ضمن الحلقة. ملاحظة: يمكن أن تكون التفاعلية خادعةً في بعض الأحيان، إذ يتعرّف إطار Svelte على المتغير filter بوصفه اعتماديةً لأننا نشير إليه في التعبيرfilterTodos(filter, todo)، إذ يُعَدّ المتغير filter متغيرًا من المستوى الأعلى، لذلك يمكن إزالته من معامِلات الدالة المساعدة واستدعائه بالشكل: filterTodos(todo)، كما يمكن أن ينجح هذا الأمر، ولكن ليس لدى إطار Svelte الآن طريقةً لمعرفة أنّ {#each filterTodos(todos)... } يعتمد على المتغير filter، ولن تُحدَّث قائمة المهام المُرشَّحة عندما يتغير المرشّح، وتذكَّر دائمًا أنّ إطار Svelte يحلِّل الشيفرة لاكتشاف الاعتماديات، لذلك يُفضَّل أن تكون صريحًا بشأنه وألّا تعتمد على رؤية متغيرات المستوى الأعلى، كما يُعَدّ جعل الشيفرة واضحةً وصريحةً بشأن المعلومات التي تستخدِمها من الممارسات الجيدة. يمكنك الوصول إلى نسختك من مستودعنا على النحو التالي لمعرفة حالة الشيفرة كما يجب أن تكون في نهاية هذا المقال: cd mdn-svelte-tutorial/04-componentizing-our-app أو يمكنك تنزيل محتوى المجلد مباشرةً باستخدام الأمر التالي: npx degit opensas/mdn-svelte-tutorial/04-componentizing-our-app تذكَّر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير، فإذا أردت متابعتنا، فابدأ بكتابة الشيفرة باستخدام الأداة REPL. الخلاصة طبّقنا في هذا المقال معظم الوظائف المطلوبة، إذ يمكن لتطبيقنا عرض وإضافة وحذف المهام وتبديل حالتها المكتملة وإظهار عدد المهام المكتملة وتطبيق المرشحات، حيث غطينا المواضيع التالية: إنشاء واستخدام المكونات. تحويل شيفرة HTML الثابتة إلى قالب حي. تضمين تعابير جافاسكربت في شيفرة HTML. التكرار على القوائم باستخدام الموجّه {#each}. تمرير المعلومات بين المكونات باستخدام الخاصيات. الاستماع إلى أحداث DOM. التصريح عن التعليمات التفاعلية. تنقيح الأخطاء الأساسي باستخدام التابع console.log() والتعليمات التفاعلية. ربط خاصيات HTML بالموجّه bind:property. بدء التفاعل باستخدام الإسنادات. استخدام العبارات التفاعلية لترشيح البيانات. التعريف الصريح عن الاعتماديات التفاعلية. سنضيف مزيدًا من الوظائف التي ستسمح للمستخدِمين بتعديل المهام في المقال التالي. ترجمة -وبتصرُّف- للمقال Dynamic behavior in Svelte: working with variables and props. اقرأ أيضًا بدء استخدام إطار العمل Svelte لبناء تطبيقات ويب إنشاء تطبيق قائمة مهام باستعمال إطار عمل Svelte
-
يمكننا الآن البدء في إنشاء تطبيقنا مثل تطبيق قائمة المهام بعد أن فهمنا الأمور الأساسية في إطار عمل Svelte في المقال السابق، إذ سنلقي في هذا المقال نظرةً على الوظائف المطلوبة لتطبيقنا أولًا ثم سننشئ المكوِّن Todos.svelte وسنضع شيفرة HTML وشيفرة التنسيق الثابتة في مكانها، وبالتالي سيصبح كل شيء جاهزًا لبدء تطوير ميزات تطبيق قائمة المهام التي سننتقل إليها في المقالات اللاحقة. نريد أن يتمكن المستخدِمون من تصفح المهام وإضافتها وحذفها ووضع علامة عليها بوصفها مكتملةً، كما سنلقي نظرةً على بعض المفاهيم الأكثر تقدمًا. المتطلبات الأساسية: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات HTML و CSS وجافاسكربت JavaScript، ومعرفة باستخدام سطر الأوامر أو الطرفية، وستحتاج طرفية مثبَّت عليها node وnpm لتصريف وبناء تطبيقك. الهدف: معرفة كيفية إنشاء مكوِّن Svelte وتصييره في مكوِّن آخر وتمرير البيانات إليه باستخدام الخاصيات Props وحفظ حالته. يمكن متابعة كتابة شيفرتك معنا، لذلك انسخ أولًا مستودع github -إذا لم تفعل ذلك مسبقًا- باستخدام الأمر التالي: git clone https://github.com/opensas/mdn-svelte-tutorial.git ثم يمكنك الوصول إلى حالة التطبيق الحالية من تشغيل الأمر التالي: cd mdn-svelte-tutorial/02-starting-our-todo-app أو يمكنك تنزيل محتوى المجلد مباشرةً كما يلي: npx degit opensas/mdn-svelte-tutorial/02-starting-our-todo-app تذكَّر تشغيل الأمر التالي لبدء تشغيل تطبيقك في وضع التطوير: npm install && npm run dev فإذا أردت متابعتنا فابدأ بكتابة الشيفرة باستخدام أداة REPL. ميزات تطبيق قائمة المهام سيبدو تطبيق قائمة المهام كما يلي بمجرد أن يصبح جاهزًا: سيتمكّن المستخدِم من تطبيق الأمور التالية باستخدام واجهة المستخدِم: تصفح المهام. وضع علامة على المهام المكتملة أو تعليقها دون حذفها. إزالة المهام. إضافة مهام جديدة. ترشيح المهام حسب الحالة: جميع المهام أو المهام النشطة أو المهام المكتملة. تعديل المهام. وضع علامة على جميع المهام بوصفها نشطةً أو مكتملةً. إزالة جميع المهام المكتملة. إنشاء المكون الأول لننشئ المكوِّن Todos.svelte الذي سيحتوي على قائمة المهام. أولًا، أنشئ مجلدًا جديدًا بالاسم src/components. ملاحظة: يمكنك وضع مكوناتك في أيّ مكان ضمن المجلد src، ولكن المجلد components هو اصطلاح معروف يجب اتباعه، مما يسمح لك بالعثور على مكوناتك بسهولة. ثانيًا، أنشئ ملفًا بالاسم src/components/Todos.svelte بحيث يحوي ما يلي: <h1>Svelte To-Do list</h1> ثالثًا، عدّل العنصر title في الملف public/index.html ليحتوي على النص "Svelte To-do list" كما يلي: <title>Svelte To-Do list</title> رابعًا، افتح الملف src/App.svelte واستبدل محتوياته بما يلي: <script> import Todos from './components/Todos.svelte' </script> <Todos /> سيصدر إطار عمل Svelte في وضع التطوير تحذيرًا في طرفية المتصفح عند تحديد خاصية غير موجودة في المكوِّن مثل تحديد الخاصية name عند إنشاء نسخة من المكون App ضمن الملف src/main.js، إذ لا تُستخدَم هذه الخاصية ضمن المكوِّن App، كما يجب أن تعطيك الطرفية حاليًا رسالة مثل الرسالة " was created with unknown prop 'name'"، لكن يمكنك حل هذه المشكلة من خلال إزالة الخاصية name من src/main.js ويجب أن يبدو الآن كما يلي: import App from './App.svelte' const app = new App({ target: document.body }) export default app إذا تحققت من عنوان URL لخادم الاختبار، فسترى تصيير المكوِّن Todos.svelte كما يلي: إضافة شيفرة HTML الثابتة سنبدأ أولًا بتمثيل شيفرة HTML لتطبيقنا لتتمكّن من رؤية الشكل الذي سيبدو عليه، لذا انسخ والصق ما يلي في ملف المكوِّن Todos.svelte ليحل محل المحتوى الموجود مسبقًا: <!-- Todos.svelte --> <div class="todoapp stack-large"> <!-- NewTodo --> <form> <h2 class="label-wrapper"> <label for="todo-0" class="label__lg"> What needs to be done? </label> </h2> <input type="text" id="todo-0" autocomplete="off" class="input input__lg" /> <button type="submit" disabled="" class="btn btn__primary btn__lg"> Add </button> </form> <!-- Filter --> <div class="filters btn-group stack-exception"> <button class="btn toggle-btn" aria-pressed="true"> <span class="visually-hidden">Show</span> <span>All</span> <span class="visually-hidden">tasks</span> </button> <button class="btn toggle-btn" aria-pressed="false"> <span class="visually-hidden">Show</span> <span>Active</span> <span class="visually-hidden">tasks</span> </button> <button class="btn toggle-btn" aria-pressed="false"> <span class="visually-hidden">Show</span> <span>Completed</span> <span class="visually-hidden">tasks</span> </button> </div> <!-- TodosStatus --> <h2 id="list-heading">2 out of 3 items completed</h2> <!-- Todos --> <ul role="list" class="todo-list stack-large" aria-labelledby="list-heading"> <!-- todo-1 (editing mode) --> <li class="todo"> <div class="stack-small"> <form class="stack-small"> <div class="form-group"> <label for="todo-1" class="todo-label"> New name for 'Create a Svelte starter app' </label> <input type="text" id="todo-1" autocomplete="off" class="todo-text" /> </div> <div class="btn-group"> <button class="btn todo-cancel" type="button"> Cancel <span class="visually-hidden">renaming Create a Svelte starter app</span> </button> <button class="btn btn__primary todo-edit" type="submit"> Save <span class="visually-hidden">new name for Create a Svelte starter app</span> </button> </div> </form> </div> </li> <!-- todo-2 --> <li class="todo"> <div class="stack-small"> <div class="c-cb"> <input type="checkbox" id="todo-2" checked/> <label for="todo-2" class="todo-label"> Create your first component </label> </div> <div class="btn-group"> <button type="button" class="btn"> Edit <span class="visually-hidden">Create your first component</span> </button> <button type="button" class="btn btn__danger"> Delete <span class="visually-hidden">Create your first component</span> </button> </div> </div> </li> <!-- todo-3 --> <li class="todo"> <div class="stack-small"> <div class="c-cb"> <input type="checkbox" id="todo-3" /> <label for="todo-3" class="todo-label"> Complete the rest of the tutorial </label> </div> <div class="btn-group"> <button type="button" class="btn"> Edit <span class="visually-hidden">Complete the rest of the tutorial</span> </button> <button type="button" class="btn btn__danger"> Delete <span class="visually-hidden">Complete the rest of the tutorial</span> </button> </div> </div> </li> </ul> <hr /> <!-- MoreActions --> <div class="btn-group"> <button type="button" class="btn btn__primary">Check all</button> <button type="button" class="btn btn__primary">Remove completed</button> </div> </div> تحقّق من الخرج المُصيَّر مرةً أخرى، وسترى شيئًا يشبه ما يلي: يُعَدّ تنسيق شيفرة HTML السابق ليس جيدًا كما أنه غير مفيد وظيفيًا، ولكن لنلقِ نظرةً على الشيفرة ونرى مدى ارتباطها بالميزات التي نرغب بها: تسمية أو عنوان Label ومربع نص لإدخال مهام جديدة. ثلاثة أزرار لترشيح المهام حسب حالتها. تسمية label توضّح العدد الإجمالي للمهام والمهام المكتملة. قائمة غير مرتبة تحتوي على عنصر قائمة لكل مهمة. يحتوي عنصر القائمة عند تعديل المهمة على حقل إدخال وزرَين لإلغاء التعديلات أو حفظها. إذا لم تكن المهمة قيد التعديل، فهناك مربع اختيار لضبط حالة المهمة المكتملة وزرَين لتعديل المهمة أو حذفها. يوجد زران لتحديد أو إلغاء تحديد جميع المهام وإزالة المهام المكتملة. سنعمل في المقالات اللاحقة على تشغيل جميع هذه الميزات. ميزات سهولة الوصول Accessibility لقائمة المهام لاحظ وجود بعض السمات غير المعتادة مثل: <button class="btn toggle-btn" aria-pressed="true"> <span class="visually-hidden">Show</span> <span>All</span> <span class="visually-hidden">tasks</span> </button> تخبر السمة aria-pressed التقنيات المساعدة مثل قارئات الشاشة أنّ الزر يمكن أن يكون في إحدى الحالتين: pressed أو unpressed مثل القول بأن الزر في وضع التشغيل أو الإيقاف، ويعني ضبط القيمة true أنّ الزر مضغوط افتراضيًا. ليس للصنف visually-hidden أيّ تأثير حتى الآن، لأننا لم نضمّن أيّ ملف CSS، وسيُخفَى أيّ عنصر موجود في هذا الصنف عن المستخدِمين المبصرين وسيظل متاحًا لمستخدِمي قارئات الشاشة بمجرد أن نضع التنسيق في مكانه، لأن هذه الكلمات لا يحتاجها المستخدِمون المبصرون، وإنما تُستخدَم لتقديم مزيد من المعلومات حول ما يفعله الزر لمستخدِمي قارئات الشاشة الذين ليس لديهم القدرة البصرية لمساعدتهم. كما يمكنك العثور على عنصر <ul> التالي: <ul role="list" className="todo-list stack-large" aria-labelledby="list-heading"> تساعد السمة role التقنيات المساعدة في توضيح نوع القيمة الدلالية للعنصر أو ما هو الغرض منه، إذ يُعامَل العنصر <ul> بوصفه قائمةً افتراضيًا، ولكن ستؤدي التنسيقات التي نريد إضافتها إلى تعطيل هذه الوظيفة، ولكن سيعيد هذا الدور معنى القائمة إلى العنصر <ul>. تخبر السمة aria-labelledby التقنيات المساعدة بأننا نتعامل مع العنصر <h2> مع معرِّف id عنوان القائمة list-heading بوصفه التسمية التي تشرح الغرض من القائمة الموجودة تحتها، إذ يعطي هذا الارتباط القائمةَ سياقًا مفيدًا، مما يساعد مستخدِمي قارئات الشاشة على فهم الغرض منها بصورة أفضل. دعم إطار عمل Svelte لسهولة الوصول يركّز إطار Svelte على سهولة الوصول أو الشمولية Accessibility، والهدف هو تشجيع المطورين على كتابة المزيد من الشيفرة البرمجية الشاملة افتراضيًا، وبما أنّ إطار Svelte يُعَدّ مصرِّفًا، فيمكنه تحليل قوالب HTML بطريقة ساكنة لتوفير تحذيرات متعلقة بسهولة الوصول عند تصريف المكونات. لا يُعَدّ عملية تطبيق مبادئ سهولة الوصول -التي تُختصَر إلى a11y- أمرًا سهلًا دائمًا، ولكن سيساعدك إطار Svelte من خلال تحذيرك إذا كتبت شيفرة HTML لا تراعي تلك المبادئ، فإذا أضفنا العنصر <img> مثلًا إلى المكوِّن todos.svelte بدون الخاصية alt المقابلة له كما يلي: <h1>Svelte To-Do list</h1> <img height="32" width="88" src="https://www.w3.org/WAI/wcag2A" /> فسيعطي المصرِّف التحذير التالي: (!) Plugin svelte: A11y: <img> element should have an alt attribute src/components/Todos.svelte 1: <h1>Svelte To-Do list</h1> 2: 3: <img height="32" width="88" src="https://www.w3.org/WAI/wcag2A"> ^ created public/build/bundle.js in 220ms [2020-07-15 04:07:43] waiting for changes… كما يمكن لمحرر الشيفرة عرض هذا التحذير حتى قبل استدعاء المصرِّف كما يلي: يمكنك إخبار إطار عمل Svelte بتجاهل هذا التحذير للكتلة التالية من شيفرة HTML بتعليق يبدأ بعبارة svelte-ignore كما يلي: <!-- svelte-ignore a11y-missing-attribute --> <img height="32" width="88" src="https://www.w3.org/WAI/wcag2A"> ملاحظة: يمكنك باستخدام المحرّر VSCode إضافة تعليق التجاهل هذا تلقائيًا بالنقر على الرابط "Quick fix…" أو بالضغط على الاختصار Ctrl + .. إذا أردت تعطيل هذا التحذير، فيمكنك إضافة المعالج onwarn إلى الملف rollup.config.js ضمن إعداد الإضافة Svelte كما يلي: plugins: [ svelte({ dev: !production, css: css => { css.write('public/build/bundle.css'); }, // Warnings are normally passed straight to Rollup. You can // optionally handle them here, for example to squelch // warnings with a particular code onwarn: (warning, handler) => { // e.g. I don't care about screen readers -> please DON'T DO THIS!!! if (warning.code === 'a11y-missing-attribute') return; // let Rollup handle all other warnings normally handler(warning); } }), ... ] تُنفَّذ هذه التحذيرات في المصرِّف نفسه حسب التصميم وليس على أساس إضافة يمكن أن تختار إضافتها إلى مشروعك، كما تكمن الفكرة في التحقق من وجود مشاكل سهولة الوصول a11y في الشيفرة افتراضيًا والسماح بإلغاء تحذيرات معينة. ملاحظة: لا يجب تعطيل هذه التحذيرات إلا إذا كانت لديك أسباب وجيهة لذلك مثل تعطيلها أثناء إنشاء نموذج أولي prototype سريع، إذ يجب أن تجعل صفحاتك قابلةً للوصول إلى أوسع قاعدة ممكنة من المستخدِمين. قواعد الشمولية التي تحقق منها إطار عمل Svelte مأخوذة من الإضافة eslint-plugin-jsx-a11y، وهي إضافة من ESLint توفِّر فحوصات ساكنة للعديد من قواعد سهولة الوصول على عناصر JSX، كما يهدف إطار Svelte إلى تنفيذ كل من هذه القواعد في مصرِّفه، وقد نُقِل معظمها إلى Svelte فعليًا، بالإضافة إلى أنه يمكنك على GitHub معرفة فحوصات الشمولية التي لا تزال مفقودة، ويمكنك التحقق من معنى كل قاعدة من خلال النقر على رابطها الخاص. تنسيق التطبيق لنجعل قائمة المهام تبدو أفضل قليلًا، لذا استبدل محتويات الملف public/global.css بما يلي: /* RESETS */ *, *::before, *::after { box-sizing: border-box; } *:focus { outline: 3px dashed #228bec; outline-offset: 0; } html { font: 62.5% / 1.15 sans-serif; } h1, h2 { margin-bottom: 0; } ul { list-style: none; padding: 0; } button { border: none; margin: 0; padding: 0; width: auto; overflow: visible; background: transparent; color: inherit; font: inherit; line-height: normal; -webkit-font-smoothing: inherit; -moz-osx-font-smoothing: inherit; -webkit-appearance: none; } button::-moz-focus-inner { border: 0; } button, input, optgroup, select, textarea { font-family: inherit; font-size: 100%; line-height: 1.15; margin: 0; } button, input { overflow: visible; } input[type="text"] { border-radius: 0; } body { width: 100%; max-width: 68rem; margin: 0 auto; font: 1.6rem/1.25 Arial, sans-serif; background-color: #f5f5f5; color: #4d4d4d; } @media screen and (min-width: 620px) { body { font-size: 1.9rem; line-height: 1.31579; } } /*END RESETS*/ /* GLOBAL STYLES */ .form-group > input[type="text"] { display: inline-block; margin-top: 0.4rem; } .btn { padding: 0.8rem 1rem 0.7rem; border: 0.2rem solid #4d4d4d; cursor: pointer; text-transform: capitalize; } .btn.toggle-btn { border-width: 1px; border-color: #d3d3d3; } .btn.toggle-btn[aria-pressed="true"] { text-decoration: underline; border-color: #4d4d4d; } .btn__danger { color: #fff; background-color: #ca3c3c; border-color: #bd2130; } .btn__filter { border-color: lightgrey; } .btn__primary { color: #fff; background-color: #000; } .btn__primary:disabled { color: darkgrey; background-color:#565656; } .btn-group { display: flex; justify-content: space-between; } .btn-group > * { flex: 1 1 49%; } .btn-group > * + * { margin-left: 0.8rem; } .label-wrapper { margin: 0; flex: 0 0 100%; text-align: center; } .visually-hidden { position: absolute !important; height: 1px; width: 1px; overflow: hidden; clip: rect(1px 1px 1px 1px); clip: rect(1px, 1px, 1px, 1px); white-space: nowrap; } [class*="stack"] > * { margin-top: 0; margin-bottom: 0; } .stack-small > * + * { margin-top: 1.25rem; } .stack-large > * + * { margin-top: 2.5rem; } @media screen and (min-width: 550px) { .stack-small > * + * { margin-top: 1.4rem; } .stack-large > * + * { margin-top: 2.8rem; } } .stack-exception { margin-top: 1.2rem; } /* END GLOBAL STYLES */ .todoapp { background: #fff; margin: 2rem 0 4rem 0; padding: 1rem; position: relative; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1); } @media screen and (min-width: 550px) { .todoapp { padding: 4rem; } } .todoapp > * { max-width: 50rem; margin-left: auto; margin-right: auto; } .todoapp > form { max-width: 100%; } .todoapp > h1 { display: block; max-width: 100%; text-align: center; margin: 0; margin-bottom: 1rem; } .label__lg { line-height: 1.01567; font-weight: 300; padding: 0.8rem; margin-bottom: 1rem; text-align: center; } .input__lg { padding: 2rem; border: 2px solid #000; } .input__lg:focus { border-color: #4d4d4d; box-shadow: inset 0 0 0 2px; } [class*="__lg"] { display: inline-block; width: 100%; font-size: 1.9rem; } [class*="__lg"]:not(:last-child) { margin-bottom: 1rem; } @media screen and (min-width: 620px) { [class*="__lg"] { font-size: 2.4rem; } } .filters { width: 100%; margin: unset auto; } /* Todo item styles */ .todo { display: flex; flex-direction: row; flex-wrap: wrap; } .todo > * { flex: 0 0 100%; } .todo-text { width: 100%; min-height: 4.4rem; padding: 0.4rem 0.8rem; border: 2px solid #565656; } .todo-text:focus { box-shadow: inset 0 0 0 2px; } /* CHECKBOX STYLES */ .c-cb { box-sizing: border-box; font-family: Arial, sans-serif; -webkit-font-smoothing: antialiased; font-weight: 400; font-size: 1.6rem; line-height: 1.25; display: block; position: relative; min-height: 44px; padding-left: 40px; clear: left; } .c-cb > label::before, .c-cb > input[type="checkbox"] { box-sizing: border-box; top: -2px; left: -2px; width: 44px; height: 44px; } .c-cb > input[type="checkbox"] { -webkit-font-smoothing: antialiased; cursor: pointer; position: absolute; z-index: 1; margin: 0; opacity: 0; } .c-cb > label { font-size: inherit; font-family: inherit; line-height: inherit; display: inline-block; margin-bottom: 0; padding: 8px 15px 5px; cursor: pointer; touch-action: manipulation; } .c-cb > label::before { content: ""; position: absolute; border: 2px solid currentColor; background: transparent; } .c-cb > input[type="checkbox"]:focus + label::before { border-width: 4px; outline: 3px dashed #228bec; } .c-cb > label::after { box-sizing: content-box; content: ""; position: absolute; top: 11px; left: 9px; width: 18px; height: 7px; transform: rotate(-45deg); border: solid; border-width: 0 0 5px 5px; border-top-color: transparent; opacity: 0; background: transparent; } .c-cb > input[type="checkbox"]:checked + label::after { opacity: 1; } يبدو كل شيء الآن أفضل كما يلي: يمكنك الوصول إلى نسختك من مستودعنا على النحو التالي لمعرفة حالة الشيفرة، كما يجب أن تكون في نهاية هذا المقال: cd mdn-svelte-tutorial/03-adding-dynamic-behavior أو يمكنك تنزيل محتوى المجلد مباشرةً باستخدام الأمر التالي: npx degit opensas/mdn-svelte-tutorial/03-adding-dynamic-behavior تذكَّر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير، فإذا أردت متابعتنا، فابدأ بكتابة الشيفرة باستخدام الأداة REPL. الخلاصة بدأ تطبيق قائمة المهام في التبلور مع تطبيق شيفرة HTML وشيفرة التنسيق CSS، وأصبح كل شيء جاهزًا لنتمكّن من التركيز على الميزات التي يجب تطبيقها. ترجمة -وبتصرُّف- للمقال Starting our Svelte to-do list app. اقرأ أيضًا المقال السابق: بدء استخدام إطار العمل Svelte لبناء تطبيقات ويب إنشاء تطبيق قائمة مهام باستخدام React إنشاء تطبيق Todo List بسيط باستخدام Laravel 5 - الجزء الأول
-
سنقدّم في هذا المقال مقدمةً سريعةً عن إطار عمل Svelte، إذ سنرى كيفية عمله وما يميزه عن باقي أطر العمل وأدواته، ثم سنتعلم كيفية إعداد بيئة التطوير وإنشاء تطبيق ويب بسيط وفهم بنية المشروع ومعرفة كيفية تشغيله محليًا وإنشائه للإنتاج. المتطلبات الأساسية: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة باستخدام سطر الأوامر أو الطرفية، إذ يُعَد إطار العمل Svelte مصرِّفًا Compiler ينشئ شيفرة جافاسكربت مُصغَّرةً ومُحسَّنةً من شيفرتنا البرمجية، لذا ستحتاج إلى طرفية مثبَّت عليها node و npm لتصريف وبناء تطبيقك. الهدف: إعداد بيئة تطوير Svelte محلية وإنشاء وبناء تطبيق بسيط وفهم أساسيات كيفية عمله. إطار عمل Svelte: طريقة جديدة لبناء واجهات المستخدم يوفِّر إطار العمل Svelte نهجًا مختلفًا لبناء تطبيقات الويب عن بعض أطر العمل الأخرى التي تحدثنا عنها في هذه السلسلة تعلم تطوير الويب مثل Ember أو Vue.js، إذ تطبّق أطر العمل مثل React أو Vue.js الجزء الأكبر من عملها في متصفح المستخدِم أثناء تشغيل التطبيق، بينما ينقل إطار Svelte العمل إلى خطوة التصريف التي لا تحدث إلا عند بناء تطبيقك، مما ينتج عنه شيفرة مُحسَّنة باستخدام لغة جافاسكربت الصرفة Vanilla JavaScript، كما ينتج عن هذا النهج حزم تطبيقات أصغر وأداء أفضل، بالإضافة إلى تجربة مطور أسهل للأشخاص الذين لديهم خبرة محدودة في النظام البيئي المجتمعي للأدوات الحديثة. يلتزم إطار عمل Svelte بنموذج تطوير الويب الكلاسيكي باستخدام اللغات HTML و CSS و JS مع إضافة بعض الامتدادات إلى HTML وجافاسكربت، إذ يمكن القول أنه لديه مفاهيم وأدوات أقل للتعلم من خيارات أطر العمل الأخرى، لكن تتمثل عيوب Svelte الرئيسية الحالية في أنه إطار عمل جديد، وبالتالي فإن نظامه البيئي محدود أكثر من الأطر الأقدم من حيث الأدوات والدعم والإضافات وأنماط الاستخدام الواضحة وما إلى ذلك، كما أنّ هناك فرص عمل أقل متعلقة به، لذلك يجب أن تكون مزاياه كافيةً للاهتمام باستخدامه. ملاحظة: أضاف إطار Svelte مؤخرًا دعم لغة TypeScript الرسمي، وهو أحد أكثر الميزات المطلوبة. حالات الاستخدام يمكن استخدام إطار عمل Svelte لتطوير أجزاء صغيرة من واجهة أو تطبيقات كاملة، إذ يمكنك إما البدء من نقطة الصفر والسماح لإطار عمل Svelte بتشغيل واجهة المستخدِم أو يمكنك دمجه مع تطبيق موجود مسبقًا، كما يُعَدّ إطار Svelte مناسبًا لمعالجة المواقف التالية: تطبيقات الويب المخصصة للأجهزة ذات الإمكانات المنخفضة: تتميز التطبيقات المُنشَأة باستخدام إطار عمل Svelte بأحجام حزم أصغر، وهي مثالية للأجهزة ذات اتصالات الشبكة البطيئة وقوة المعالجة المحدودة، إذ يؤدي استخدام شيفرة برمجية أقل إلى استخدام كيلوبايتات أقل لتنزيلها وتحليلها وتنفيذها والاستمرار في التنقل ضمن الذاكرة بسلاسة. الصفحات التفاعلية جدًا أو ذات المؤثرات البصرية المعقدة: إذا أردت بناء مؤثرات البيانات البصرية التي تحتاج لعرض عدد كبير من عناصر نموذج DOM، فستضمن مكاسب الأداء التي تأتي من إطار عمل بدون تكاليف تشغيل إضافية أن تكون تفاعلات المستخدِم ذات استجابة سريعة. تأهيل الأشخاص ذوي المعرفة الأساسية بتطوير الويب: يتمتع إطار Svelte بمنحنى تعليمي سطحي، إذ يمكن لمطوري الويب الذين لديهم معرفة أساسية بلغات HTML و CSS وجافاسكربت استيعاب تفاصيل إطار Svelte بسهولة في وقت قصير والبدء في إنشاء تطبيقات الويب. يساعد إطار عمل Sapper الذي يعتمد على إطار عمل Svelte في تطوير تطبيقات ذات ميزات متقدمة مثل التصيير من طرف الخادم Server-side Rendering وتقسيم الشيفرة والتوجيه المستند إلى الملفات والدعم دون الاتصال بالإنترنت، وهناك إطار عمل Svelte Native الذي يتيح بناء تطبيقات هاتف محمول أصيلة Native. كيفية عمل إطار عمل Svelte يمكن لإطار عمل Svelte توسيع لغات HTML و CSS وجافاسكربت نظرًا لكونه مصرِّفًا، مما يؤدي إلى إنشاء شيفرة جافاسكربت مثالية دون أيّ تكاليف تشغيل إضافية، إذ يوسّع إطار عمل Svelte تقنيات الويب الصرفة بالطرق التالية: يوسّع لغة HTML عن طريق السماح بتعابير جافاسكربت في شيفرة التوصيف وتوفير الموجّهات لاستخدام الشروط والحلقات بطريقة تشبه لغة Handlebars. يوسّع لغة CSS عن طريق إضافة آلية تحديد نطاق، مما يسمح لكل مكوِّن بتحديد تنسيقه الخاص دون التعرض لخطر التعارض مع تنسيق المكونات الأخرى. يوسّع لغة جافاسكربت من خلال إعادة تفسير موجّهات محددة للغة لتحقيق تفاعل حقيقي وتسهيل إدارة حالة المكوِّن. يتدخل المصرِّف فقط في مواقف محددة للغاية وفي سياق مكونات Svelte، كما تُعَدّ الامتدادات في لغة جافاسكربت قليلةً وتُنتَقى بعناية بهدف عدم تغيير صيغة جافاسكربت أو إبعاد المطورين، إذ ستعمل باستخدام لغة جافاسكربت الصرفة Vanilla JavaScript في أغلب الأحيان. الخطوات الأولى لاستخدام إطار Svelte لا يمكنك إضافة الوسم <script src="svelte.js"> إلى صفحتك واستيرادها إلى تطبيقك فقط، إذ سيتعين عليك إعداد بيئة التطوير للسماح للمصرِّف بتطبيق عمله. المتطلبات يجب تثبيت Node.js للعمل مع إطار عمل Svelte، إذ يوصَى باستخدام إصدار الدعم طويل الأمد LTS، كما يتضمن Node مدير الحزم npm ومشغّل الحزم npx. لاحظ أنه يمكنك استخدام مدير الحزم Yarn بدلًا من npm، لكننا سنفترض أنك تستخدِم npm في هذا المقال، ويمكنك مراجعة مقال أساسيات إدارة الحزم لمزيد من المعلومات حول npm وyarn. إذا استخدَمت نظام ويندوز، فيجب عليك تثبيت بعض البرامج لمنحك التكافؤ مع طرفية نظامَي يونكس Unix أو ماك macOS من أجل استخدام أوامر الطرفية المذكورة في هذا المقال، إذ يُعَدّ كل من Gitbash الذي يأتي على أساس جزء من مجموعة أدوات git لنظام ويندوز أو نظام ويندوز الفرعي لنظام لينكس -WSL اختصارًا- مناسبين، كما يُعَدّ برنامج Cmder بديلًا آخر جيدًا وكاملًا، ويمكنك مراجعة مقال سطر الأوامر للحصول على مزيد من المعلومات حول هذه الأوامر وأوامر الطرفية. إنشاء تطبيق Svelte الأول أسهل طريقة لإنشاء قالب تطبيق بسيط هي مجرد تنزيل قالب تطبيق البدء من خلال زيارة صفحة sveltejs/template على GitHub أو يمكنك تجنب الاضطرار إلى تنزيله وفك ضغطه واستخدام أداة degit فقط. أنشئ قالب تطبيق البدء وشغّل أوامر الطرفية التالية: npx degit sveltejs/template moz-todo-svelte cd moz-todo-svelte npm install npm run dev ملاحظة: تتيح degit تنزيل أحدث إصدار من محتويات مستودع Git وفك ضغطه، وهذا أسرع بكثير من استخدام git clone لأنه لن ينزّل كل محفوظات المستودع أو ينشئ نسخةً محليةً كاملةً. سيصرّف إطار عمل Svelte التطبيق ويبنيه بعد تشغيل الأمر npm run dev، كما سيشغّل خادمًا محليًا على المضيف المحلي localhost:8080، إذ يراقب Svelte تحديثات الملفات، ويعيد تلقائيًا تصريف وتحديث التطبيق نيابةً عنك عند إجراء تغييرات على الملفات المصدرية، ثم سيعرض متصفحك شيئًا يشبه ما يلي: بنية التطبيق يأتي قالب البدء بالبنية التالية: moz-todo-svelte ├── README.md ├── package.json ├── package-lock.json ├── rollup.config.js ├── .gitignore ├── node_modules ├── public │ ├── favicon.png │ ├── index.html │ ├── global.css │ └── build │ ├── bundle.css │ ├── bundle.js │ └── bundle.js.map ├── scripts │ └── setupTypeScript.js └── src ├── App.svelte └── main.js يتكون من المحتويات التالية: الملفان package.json و package-lock.json: يحتويان على معلومات حول المشروع التي يستخدمها Node.js ومدير الحزم npm لإبقاء المشروع منظمًا، ولا تحتاج إلى فهم هذين الملفين على الإطلاق، لكن إذا أردت معرفة المزيد عنهما، فاطلع على مقال أساسيات إدارة الحزم. node_modules: هو المكان الذي تحفظ فيه Node اعتماديات المشروع، ولن تُرسَل هذه الاعتماديات إلى مرحلة الإنتاج، وإنما ستُستخدَم فقط لأغراض التطوير. .gitignore: يحدِّد git الملفات أو المجلدات التي يجب تجاهلها من المشروع، وهذا مفيد إذا قررت تضمين تطبيقك في مستودع git. rollup.config.js: يستخدِم إطار عمل Svelte مجمّع الوحدات rollup.js، كما يوضّح ملف الإعداد كيفية تجميع وبناء تطبيقك، وإذا فضلت استخدام أداة Webpack، فيمكنك إنشاء مشروعك باستخدام الأمر npx degit sveltejs/template-webpack svelte-app بدلًا من ذلك. scripts: يحتوي على سكربتات الإعداد المطلوبة، ويجب أن يحتوي حاليًا على الملف setupTypeScript.js فقط. setupTypeScript.js: يضبط هذا السكربت دعم لغة TypeScript في إطارعمل Svelte. src: هذا المجلد هو المكان الذي توجد فيه شيفرة تطبيقك البرمجية، أي حيث ستنشئ شيفرة تطبيقك. App.svelte: هو مكوّن المستوى الأعلى لتطبيقك، إذ يصيّر حتى الآن الرسالة "Hello World!". main.js: نقطة الدخول إلى التطبيق. ينشئ نسخةً من المكون App ويربطها بجسم صفحة html. public: يحتوي هذا المجلد على جميع الملفات التي ستُنشَر في مرحلة الإنتاج. favicon.png: الرمز المفضل لتطبيقك، وهو شعار Svelte حاليًا. index.html: الصفحة الرئيسية لتطبيقك، وهي في البداية مجرد صفحة HTML5 فارغة تحمّل ملفات CSS وحزم JS التي ينشئها إطار عمل Svelte. global.css: يحتوي هذا الملف على تنسيقات غير محددة النطاق، وهو ملف CSS الذي سيُطبَّق على التطبيق بأكمله. build: يحتوي هذا المجلد على شيفرة CSS وجافاسكربت الناتجة. bundle.css: ملف CSS الذي أنشأه إطار عمل Svelte من التنسيقات المُعرَّفة لكل مكوّن. bundle.js: ملف جافاسكربت المُصرَّف من كل شيفرة جافسكربت المصدرية. مكون Svelte الأول المكونات هي اللبنات الأساسية لتطبيقات Svelte وتُكتَب في الملفات ذات اللاحقة .svelte باستخدام مجموعة شاملة من شيفرة HTML، كما تُعَدّ جميع الأقسام الثلاثة <script> و <style> والتوصيف Markup أقسامًا اختياريةً ويمكن أن تظهر بأيّ ترتيب تريده. <script> // ضع شيفرتك البرمجية هنا </script> <style> /* ضع تنسيقاتك هنا */ </style> <-- ضع التوصيف (أي عناصر HTML) هنا --!> لنلقِ نظرةً على الملف src/App.svelte المرفق مع قالب البداية، حيث يجب أن ترى شيئًا يشبه ما يلي: <script> export let name; </script> <main> <h1>Hello {name}!</h1> <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p> </main> <style> main { text-align: center; padding: 1em; max-width: 240px; margin: 0 auto; } h1 { color: #ff3e00; text-transform: uppercase; font-size: 4em; font-weight: 100; } @media (min-width: 640px) { main { max-width: none; } } </style> القسم <script> تحتوي كتلة <script> على شيفرة جافاسكربت التي تُشغَّل عند إنشاء نسخة من المكوِّن، إذ تكون المتغيرات المصرَّح عنها أو المستوردة في المستوى الأعلى مرئيةً من شيفرة توصيف المكوِّن، وتُعَدّ متغيرات المستوى الأعلى الطريقة التي يتعامل بها إطار عمل Svelte مع حالة المكوِّن وتكون تفاعليةً افتراضيًا، إذ سنشرح بالتفصيل ما يعنيه ذلك لاحقًا. <script> export let name; </script> يستخدِم إطار العمل Svelte الكلمة export للتصريح عن المتغير بوصفه خاصيةً Prop، مما يعني أنه يصبح بإمكان مستخدِمي المكوِّن -مثل المكونات الأخرى- الوصول إليه، ويمثل هذا المثال توسعة إطار عمل Svelte لصيغة لغة جافاسكربت لجعلها أكثر فائدةً مع بقائها مألوفة. قسم التوصيف يمكنك إدراج أيّ شيفرة HTML تريدها في قسم التوصيف، كما يمكنك إدراج تعبير جافاسكربت صالح ضمن أقواس معقوصة مفردة {}، وسنضمّن في حالتنا قيمة الخاصية name بعد النص Hello مباشرةً. <main> <h1>Hello {name}!</h1> <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p> </main> كما يدعم إطار عمل Svelte وسومًا مثل {#if...} و {#each...} و {#await...} التي تتيح لك تصييرًا مشروطًا لجزء من شيفرة التوصيف والتكرار على قائمة من العناصر والعمل بقيم غير متزامنة. القسم <style> إذا كانت لديك خبرة في العمل مع لغة CSS، فيجب أن يكون جزء الشيفرة التالية مفهومًا: <style> main { text-align: center; padding: 1em; max-width: 240px; margin: 0 auto; } h1 { color: #ff3e00; text-transform: uppercase; font-size: 4em; font-weight: 100; } @media (min-width: 640px) { main { max-width: none; } } </style> سنطبِّق تنسيقًا على العنصر <h1>، لذلك لا بدّ أنك تتساءل عمّا سيحدث للمكونات الأخرى التي تحتوي على عناصر <h1> ضمنها. يُحدَّد في إطار عمل Svelte نطاق شيفرة CSS ضمن كتلة <style> الخاصة بمكوِّن ما لهذا المكوِّن فقط من خلال إضافة صنف Class إلى العناصر المحدَّدة، ولا يضاف هذا الصنف عشوائيًا، وإنما يعتمد على قيمة مُعمَّاة Hash خاصة بتنسيق هذا المكوِّن. يمكنك رؤية جميع هذه الأمور عمليًا من خلال فتح المضيف المحلي localhost:5042 في تبويب متصفح جديد، ثم الضغط بزر الفأرة الأيمن أو الضغط على مفتاح Ctrl على العنوان HELLO WORLD! وتحديد الخيار فحص Inspect: يغيِّر إطار العمل Svelte عند تصريف التطبيق تعريف تنسيق العنصر h1 إلى h1.svelte-1tky8bj، ثم يعدِّل كل عنصر <h1> في المكوِّن إلى الشكل <h1 class="svelte-1tky8bj"> بحيث يُطبَّق التنسيق على العنصر الخاص بالمكوِّن المحدَّد فقط. ملاحظة: يمكنك تغيير هذا السلوك وتطبيق التنسيق على محدد Selector بطريقة عامة باستخدام المعدِّل :global(...). إجراء بعض التغييرات يمكنك تعديل المكوِّن App.svelte مثل تعديل العنصر <h1> في السطر رقم 6 من المكوِّن App.svelte بحيث يكون كما يلي: <h1>Hello {name} from MDN!</h1> يؤدي حفظ التعديلات إلى حفظ التطبيق المُشغَّل على المضيف المحلي localhost:5042 تلقائيًا. التفاعل في إطار العمل Svelte يعني التفاعل Reactivity في سياق إطار عمل واجهة المستخدِم أنّ إطار العمل يمكنه تلقائيًا تحديث نموذج DOM عند تعديل حالة أيّ مكون، إذ يُشغَّل التفاعل في إطار عمل Svelte عن طريق إسناد قيمة جديدة لأيّ متغير في المستوى الأعلى ضمن أحد المكونات، فيمكننا مثلًا تضمين دالة toggleName() في المكوِّن App وزر لتشغيلها. عدّل القسمين <script> والتوصيف كما يلي: <script> export let name; function toggleName() { if (name === 'world') { name = 'svelte' } else { name = 'world' } } </script> <main> <h1>Hello {name}!</h1> <button on:click={toggleName}>Toggle name</button> <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p> </main> ينفّذ إطار العمل Svelte الدالة toggleName() عند النقر على الزر، مما يؤدي إلى تحديث قيمة المتغير name. يُحدَّث عنوان Label العنصر <h1> تلقائيًا، إذ ينشئ إطار Svelte شيفرة جافاسكربت لتحديث نموذج DOM كلما تغيرت قيمة المتغير name دون استخدام نموذج DOM الافتراضي أو أيّ آلية توافق معقدة أخرى، ولاحظ استخدام : في on:click التي تُعَدّ صيغة Svelte للاستماع إلى أحداث DOM. فحص main.js: نقطة الدخول إلى التطبيق افتح الملف src/main.js حيث يُستورَد ويُستخدَم المكوِّن App، إذ يُعَدّ هذا الملف نقطة الدخول لتطبيقنا، ويبدو في البداية كما يلي: import App from './App.svelte'; const app = new App({ target: document.body, props: { name: 'world' } }); export default app; يبدأ الملف main.js باستيراد مكوِّن Svelte الذي سنستخدِمه، ثم ينشئ نسخةً منه في السطر رقم 3، ويمرّر كائنًا له الخاصيات التالية: target: عنصر DOM الذي نريد تصيير المكوِّن ضمنه، وهو العنصر <body> في هذه الحالة. props: القيم المراد إسنادها لكل خاصية للمكوِّن App. نظرة إلى خلفية إطار Svelte لا بدّ أنك تتساءل عن كيفية تمكّن إطار Svelte من جعل كل هذه الملفات تعمل مع بعضها البعض بطريقة صحيحة، إذ يعالِج مصرّف Svelte القسم <style> لكل مكوِّن ويصرّفه في الملف public/build/bundle.css، ويصرّف قسمَي التوصيف و<script> لكل مكوِّن ويخزن النتيجة في الملف public/build/bundle.js، كما يضيف الشيفرة البرمجية في الملف src/main.js للإشارة إلى ميزات كل مكوِّن يتضمن الملف public/index.html الملفين bundle.css و bundle.js: <!DOCTYPE html> <html lang="en"> <head> <meta charset='utf-8'> <meta name='viewport' content='width=device-width,initial-scale=1'> <title>Svelte app</title> <link rel='icon' type='image/png' href='/favicon.png'> <link rel='stylesheet' href='/global.css'> <link rel='stylesheet' href='/build/bundle.css'> <script defer src='/build/bundle.js'></script> </head> <body> </body> </html> حجم الإصدار المصغر من الملف bundle.js أكثر بقليل من 3 كيلوبايت، والذي يتضمن "وقت تشغيل Svelte" -أي 300 سطر فقط من شيفرة جافاسكربت- والمكوِّن المُصرَّف App.svelte، كما يُعَدّ الملف bundle.js ملف جافاسكربت الوحيد الذي يشير إليه الملف index.html، ولا توجد مكتبات أخرى مُحمَّلة في صفحة الويب. تُعَدّ هذه المساحة أصغر بكثير من الحزم المُصرَّفة في أطر عمل أخرى، وضَع في الحسبان أنه لا يقتصر الأمر على حجم الملفات التي يجب تنزيلها في حالة تجميع الشيفرة البرمجية، والتي هي شيفرة قابلة للتنفيذ يجب تحليلها وتنفيذها والاحتفاظ بها في الذاكرة، لذلك يحدث ذلك فرقًا حقًا خاصةً في الأجهزة ذات الإمكانات المنخفضة أو التطبيقات ذات الاستخدام الكبير لوحدة المعالجة المركزية. متابعة هذه السلسلة من المقالات سنبني في هذه السلسلة من المقالات تطبيق ويب كامل، لذلك وفّرنا مستودع GitHub مع مجلد يحتوي على شيفرة التطبيق البرمجية الكاملة، كما يمكنك قراءة المحتوى فقط للحصول على فهم جيد لميزات Svelte، ولكن ستحصل على أقصى استفادة من هذا السلسلة من المقالات إذا اتبعت شيفرة التطبيق معنا، كما سنوفر مستودع GitHub مع مجلد يحتوي على شيفرة التطبيق البرمجية كما هي في بداية كل مقال لتسهيل متابعته. يوفِّر إطار Svelte أداة REPL على الإنترنت، وهي أداة لتطبيقات Svelte ذات الشيفرة الحية المباشرة على الويب دون الحاجة إلى تثبيت أيّ شيء على جهازك، ولنتحدث الآن عن كيفية استخدام هذه الأدوات. استخدام Git يُعَدّ Git أكثر أنظمة التحكم في الإصدارات شيوعًا مع GitHub، وهو موقع يوفِّر استضافةً لمستودعاتك والعديد من الأدوات للعمل بها، وسنستخدِم GitHub لتتمكّن من تنزيل الشيفرة البرمجية بسهولة. يجب تنفيذ الأمرالتالي بعد تثبيت Git لنسخ المستودع: git clone https://github.com/opensas/mdn-svelte-tutorial.git يمكنك إدخال الأمر cd في المجلد المقابل وبدء تشغيل التطبيق في وضع التطوير dev لترى ما يجب أن تكون عليه حالة التطبيق الحالية كما يلي: cd 02-starting-our-todo-app npm install npm run dev ملاحظة: إذا أردت تنزيل الملفات فقط دون نسخ مستودع Git، فيمكنك استخدام الأداة degit في الأمر npx degit opensas/mdn-svelte-tutorial، كما يمكنك تنزيل مجلد محدد باستخدام الأمر npx degit opensas/mdn-svelte-tutorial/01-getting-started، ولن تنشئ الأداة degit مستودع git محلي، وإنما ستنزّل ملفات المجلد المحدَّد فقط. استخدام أداة REPL في إطار عمل Svelte تُعَدّ أداة REPL (أي حلقة قراءة-تقييم-طباعة read–eval–print Loop) بيئةً تفاعليةً تسمح بإدخال الأوامر والاطلاع على النتائج مباشرةً، وتوفِّر العديد من لغات البرمجة أداة REPL، كما تُعَدّ حلقة REPL في إطار Svelte أداةً عبر الإنترنت تتيح إنشاء تطبيقات كاملة وحفظها عبر الإنترنت ومشاركتها مع الآخرين، إذ تُعَدّ أسهل طريقة لبدء العمل باستخدام Svelte من أيّ جهاز دون الحاجة إلى تثبيت أيّ شيء، كما يستخدمها مجتمع Svelte على نطاق واسع، لذا إذا أردت مشاركة فكرة أو طلب المساعدة أو الإبلاغ عن مشكلة، فيمكنك إنشاء نسخة REPL توضِّح المشكلة. لنلقِ نظرةً سريعةً على أداة REPL في إطار عمل Svelte وكيفية استخدامها، حيث تبدو كما يلي: افتح المتصفح وانتقل إلى أداة REPL. لنتعرف على محتوياتها: سترى شيفرة مكوناتك على الجانب الأيسر من الشاشة، وسترى على اليمين خرج تنفيذ تطبيقك. يتيح لك الشريط الموجود أعلى الشيفرة إنشاء ملفات .svelte و .js وإعادة تنظيمها، كما يمكنك إنشاء ملف ضمن مجلد من خلال تحديد اسم المسار الكامل components/MyComponent.svelte ثم سيُنشَأ المجلد تلقائيًا. يوجد عنوان أداة REPL فوق هذا الشريط. يمكنك الضغط عليه لتعديله. يوجد على الجانب الأيمن ثلاث تبويبات هي: يعرض تبويب "النتيجة Result" خرج التطبيق، ويوفِّر طرفيةً Console في الأسفل. يتيح تبويب "JS output" فحص شيفرة جافاسكربت التي أنشأها إطار عمل Svelte ويضبط خيارات المصرّف Compiler. يعرض تبويب '"CSS Output" شيفرة CSS التي أنشأها إطار عمل Svelte. ستجد شريط أدوات فوق التبويبات، حيث يتيح شريط الأدوات الدخول إلى وضع ملء الشاشة وتنزيل تطبيقك، فإذا سجّلتَ الدخول باستخدام حساب GitHub، فستتمكّن من نسخ التطبيق وحفظه، وستتمكن من رؤية جميع أدوات REPL المحفوظة من خلال النقر على اسم مستخدِم حسابك على GitHub وتحديد التطبيقات المحفوظة. كلما عدّلت أيّ ملف على REPL، فسيعيد إطار عمل Svelte تصريف التطبيق وتحديث تبويب النتيجة، كما يمكنك مشاركة تطبيقك من خلال مشاركة عنوان URL مثل رابط REPL الخاص بتشغيل تطبيقنا الكامل. ملاحظة: لاحظ كيف يمكنك تحديد إصدار Svelte في عنوان URL، إذ يكون ذلك مفيدًا عند الإبلاغ عن المشاكل المتعلقة بإصدار معيّن من إطار Svelte. ملاحظة: لا يمكن لأداة REPL حاليًا التعامل مع أسماء المجلدات بطريقة صحيحة، فإذا أردت متابعة الخطوات التي نطبّقها، فأنشئ جميع مكوناتك ضمن المجلد الجذر، فإذا رأيت مسارًا في الشيفرة import Todos from './components/Todos.svelte'، فضَع مكانه عنوان URL مسطح Flat مثل import Todos from './Todos.svelte'. يمكنك نسخ مستودع github -إذا لم تفعل ذلك مسبقًا- باستخدام الأمر التالي: git clone https://github.com/opensas/mdn-svelte-tutorial.git ثم يمكنك الوصول إلى حالة التطبيق الحالية من خلال تشغيل الأمر التالي: cd mdn-svelte-tutorial/01-getting-started أو يمكنك تنزيل محتوى المجلد مباشرةً كما يلي: npx degit opensas/mdn-svelte-tutorial/01-getting-started تذكَّر تشغيل الأمر npm install && npm run dev لبدء تطبيقك في وضع التطوير، فإذا أردت متابعتنا، فابدأ بكتابة الشيفرة باستخدام الأداة REPL من هنا. الخلاصة ألقينا في هذا المقال النظرة الأولية إلى إطار عمل Svelte بما في ذلك كيفية تثبيته محليًا وإنشاء تطبيق بدء بسيط وكيفية عمل الأساسيات، كما سنبدأ في المقال التالي ببناء أول تطبيق وهو تطبيق قائمة المهام، ولنلخص بعض الأشياء التي تعلمناها في Svelte وهي: تعريف سكربت وتنسيق وتوصيف كل مكون في ملف .svelte واحد. يُصرَّح عن خاصيات المكونات باستخدام الكلمة export. يمكن استخدام مكونات Svelte فقط عن طريق استيراد ملف .svelte المقابل. يجب تحديد نطاق تنسيق المكونات، مما يمنعها من التضارب مع بعضها البعض. يمكنك تضمين أيّ تعبير جافاسكربت في قسم التوصيف Markup بوضع هذا التعبير بين قوسين معقوصين. تشكّل متغيرات المستوى الأعلى للمكوِّن حالته. يمكن إطلاق التفاعل عن طريق إسناد قيمة جديدة لمتغير المستوى الأعلى. ترجمة -وبتصرُّف- للمقال Getting started with Svelte. اقرأ أيضًا استخدام أطر العمل في برمجة تطبيقات الويب: فلاسك نموذجا مقدمة في بناء تطبيقات الويب باستخدام إطار العمل Angular وقاعدة بيانات Firestore أساسيات بناء التطبيقات في إطار العمل Laravel 5 البدء مع إطار العمل جانغو لإنشاء تطبيق ويب.