مقدمة سريعة إلى Svelte من منظور المطور الزاوي

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



نظرًا لأن تجربتي الرئيسية في مجال التطوير مرتبطة بـ Angular ، فمن الطبيعي أن أحاول تعلم Svelte عن طريق نسخ الأساليب المألوفة بالفعل لي. وهذا هو بالضبط ما ستكون عليه هذه المقالة: كيفية القيام بنفس الأشياء في Svelte كما في Angular.


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


المفسد تحذير: Svelte هو متعة.


المكونات


في Svelte ، يرتبط كل مكون بالملف حيث يتم كتابته. على سبيل المثال ، سيتم إنشاء مكون Button عن طريق تسمية ملف Button.svelte . بالطبع ، عادة ما نفعل الشيء نفسه في الزاوي ، ولكن معنا هو مجرد اتفاقية. (في Svelte ، قد لا يتطابق أيضًا اسم المكون المراد استيراده مع اسم الملف - ملاحظة من المترجم)


مكونات Svelte هي ملف واحد ، وتتكون من 3 أقسام: script ، style والقالب الذي لا يحتاج إلى أن يكون ملفوفًا في أي علامة خاصة.


لنقم بإنشاء مكون بسيط جدًا يعرض "Hello World".


hello_world


استيراد المكونات


بشكل عام ، يشبه هذا استيراد ملف JS ، ولكن مع اثنين من المحاذير:


  • يجب عليك تحديد .svelte ملف المكون .svelte بشكل صريح
  • يتم استيراد المكونات داخل علامة <script>

 <script> import Todo from './Todo.svelte'; </script> <Todo></Todo> 

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


بناء الجملة الأساسية


استيفاء


التشابهات في Svelte تشبه تلك الموجودة في React أكثر من Vue أو Angular:


 <script> let someFunction = () => {...} </script> <span>{ 3 + 5 }</span> <span>{ someFunction() }</span> <span>{ someFunction() ? 0 : 1 }</span> 

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


سمات


تمرير سمات للمكونات هو أيضا بسيط جدا. علامات الاقتباس اختيارية ويمكن استخدام أي تعبيرات Javascript:


 //Svelte <script> let isFormValid = true; </script> <button disabled={!isFormValid}></button> 

أحداث


بناء جملة معالجات الأحداث هو: on:={} .


 <script> const onChange = (e) => console.log(e); </script> <input on:input={onChange} /> 

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


 <input on:input={(e) => onChange(e, 'a')} /> 

وجهة نظري حول سهولة قراءة هذا الرمز:


  • يجب أن نطبع أقل ، لأننا لسنا بحاجة إلى علامات اقتباس وأقواس - هذا أمر جيد على أي حال.
  • قراءة أصعب. لطالما أحببت النهج الزاوي بدلاً من الرد ، لذا من الصعب بالنسبة لي و Svelte. ولكن هذه مجرد عادتي ورأيي متحيز إلى حد ما.

التوجيهات الهيكلية


على عكس التوجيهات المهيكلة في Vue and Angular ، تقدم Svelte بناء جملة خاص للحلقات والفروع داخل القوالب:


 {#if todos.length === 0}    {:else} {#each todos as todo} <Todo {todo} /> {/each} {/if} 

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


خصائص الإدخال


يعد تحديد الخصائص التي يمكن نقلها إلى مكون (مماثل لـ @Input in Angular) سهلاً مثل تصدير متغير من وحدة JS باستخدام الكلمة الأساسية export . ربما في البداية قد يكون الأمر مربكًا - ولكن دعنا نكتب مثالًا ونرى كم هو بسيط حقًا:


 <script> export let todo = { name: '', done: false }; </script> <p> { todo.name } { todo.done ? '✓' : '✕' } </p> 

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

قم الآن بإنشاء حاوية لهذا المكون ، والتي ستنقل البيانات إليها:


 <script> import Todo from './Todo.svelte'; const todos = [{ name: " Svelte", done: false }, { name: " Vue", done: false }]; </script> {#each todos as todo} <Todo todo={todo}></Todo> {/each} 

على غرار الحقول الموجودة في كائن JS عادي ، يمكن اختصار todo={todo} وإعادة كتابته كما يلي:


 <Todo {todo}></Todo> 

في البداية بدا لي غريباً ، لكن الآن أعتقد أنه رائع.


خصائص الانتاج


لتنفيذ سلوك توجيه @Output ، على سبيل المثال ، عندما @Output المكون الأصل أي إعلامات من الطفل ، createEventDispatcher الدالة createEventDispatcher ، والتي تتوفر في Svelte.


  • قم باستيراد دالة createEventDispatcher وقم بتعيين قيمة الإرجاع إلى متغير dispatch
  • تحتوي وظيفة dispatch على معلمتين: اسم الحدث والبيانات (التي ستقع في حقل detail الخاص بكائن الحدث)
  • markDone dispatch داخل وظيفة markDone ، والتي يطلق عليها حدث النقر ( on:click )

 <script> import { createEventDispatcher } from 'svelte'; export let todo; const dispatch = createEventDispatcher(); function markDone() { dispatch('done', todo.name); } </script> <p> { todo.name } { todo.done ? '✓' : '✕' } <button on:click={markDone}></button> </p> 

في المكون الأصل ، تحتاج إلى إنشاء معالج للحدث done بحيث يمكنك وضع علامة على الكائنات الضرورية في صفيف todo .


  • إنشاء وظيفة onDone
  • نقوم بتعيين هذه الوظيفة إلى معالج الأحداث المسمى في المكون on:done={onDone} ، كما يلي: on:done={onDone}

 <script> import Todo from './Todo.svelte'; let todos = [{ name: " Svelte", done: false }, { name: " Vue", done: false }]; function onDone(event) { const name = event.detail; todos = todos.map((todo) => { return todo.name === name ? {...todo, done: true} : todo; }); } </script> {#each todos as todo} <Todo {todo} on:done={onDone}></Todo> {/each} 

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


لذلك ، يعتبر Svelte تفاعليًا حقيقيًا: مع التخصيص المعتاد لقيمة ما لمتغير ، سيتغير أيضًا الجزء المقابل من التمثيل.


ngModel


يحتوي Svelte على بناء جملة bind:<>={} خاص bind:<>={} لربط متغيرات محددة بسمات مكون ومزامنتها مع بعضها البعض.


بمعنى آخر ، يسمح لك بتنظيم ربط بيانات ثنائي الاتجاه:


 <script> let name = ""; let description = ""; function submit(e) { //    } </script> <form on:submit={submit}> <div> <input placeholder="" bind:value={name} /> </div> <div> <input placeholder="" bind:value={description} /> </div> <button> </button> </form> 

التعبيرات التفاعلية


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


على سبيل المثال ، لنقم بإنشاء متغير من شأنه أن يوضح لنا أنه في صفيف todos تم وضع علامة على جميع المهام على أنها مكتملة:


 let allDone = todos.every(({ done }) => done); 

ومع ذلك ، لن يتم إعادة رسم العرض عندما يتم تحديث الصفيف ، لأنه allDone تعيين قيمة المتغير allDone مرة واحدة فقط. سنستخدم تعبيرًا تفاعليًا ، والذي سيذكرنا في الوقت نفسه بوجود "علامات" في Javascript:


 $: allDone = todos.every(({ done }) => done); 

يبدو غريبا جدا. إذا بدا لك أن هناك "الكثير من السحر" ، فأنا أذكرك بأن العلامات صالحة لجافا سكريبت .


عرض توضيحي صغير يشرح ما يلي:
demo


حقن المحتوى


لتضمين المحتوى ، يتم أيضًا استخدام فتحات يتم وضعها في المكان الصحيح داخل المكون.


لعرض المحتوى الذي تم نقله داخل عنصر المكون ، يتم استخدام عنصر slot خاص:


 // Button.svelte <script> export let type; </script> <button class.type={type}> <slot></slot> </button> // App.svelte <script> import Button from './Button.svelte'; </script> <Button>  </Button> 

في هذه الحالة ، سيأخذ "" مكان عنصر <slot></slot> .
ستحتاج الفتحات المسماة إلى تسمية:


 // Modal.svelte <div class='modal'> <div class="modal-header"> <slot name="header"></slot> </div> <div class="modal-body"> <slot name="body"></slot> </div> </div> // App.svelte <script> import Modal from './Modal.svelte'; </script> <Modal> <div slot="header">  </div> <div slot="body">  </div> </Modal> 

السنانير دورة حياة


توفر Svelte 4 خطافات لدورة الحياة التي يتم استيرادها من حزمة svelte .


  • onMount - يسمى عند تركيب مكون في DOM
  • قبل التحديث - يسمى قبل تحديث المكون
  • afterUpdate - يسمى بعد تحديث المكون
  • onDestroy - يسمى عند إزالة مكون من DOM

تأخذ وظيفة onMount كمعلمة وظيفة رد الاتصال التي سيتم استدعاؤها عند وضع المكون في DOM. ببساطة ، إنه يشبه ngOnInit ربط ngOnInit .


إذا أرجعت وظيفة رد الاتصال وظيفة أخرى ، فسيتم استدعاؤها عند إزالة المكون من DOM.


 <script> import { onMount, beforeUpdate, afterUpdate, onDestroy } from 'svelte'; onMount(() => console.log('', todo)); afterUpdate(() => console.log('', todo)); beforeUpdate(() => console.log('  ', todo)); onDestroy(() => console.log('', todo)); </script> 

من المهم أن تتذكر أنه عند استدعاء onMount يجب تهيئة جميع الخصائص المضمنة فيه بالفعل. هذا ، في الجزء أعلاه ، يجب أن يكون موجود بالفعل.


إدارة الدولة


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


خزائن قابلة للتسجيل


تحتاج أولاً إلى استيراد writable التخزين writable من حزمة svelte/store initialState بالقيمة الأولية initialState


 import { writable } from 'svelte/store'; const initialState = [{ name: " Svelte", done: false }, { name: " Vue", done: false }]; const todos = writable(initialState); 

عادةً ما أضع رمزًا مشابهًا في ملف منفصل مثل todos.store.js وقم بتصدير متغير التخزين منه حتى يتمكن المكون الذي todos.store.js من العمل به.


من الواضح أن كائن todos أصبح الآن مستودعًا ولم يعد صفيفًا. للحصول على قيمة التخزين ، سنستخدم سحرًا صغيرًا في Svelte:


  • بإضافة الحرف $ إلى اسم متغير التخزين ، يمكننا الوصول مباشرة إلى قيمتها!

وبالتالي ، فإننا ببساطة نستبدل في الكود جميع الإشارات إلى متغير todos بـ $todos :


 {#each $todos as todo} <Todo todo={todo} on:done={onDone}></Todo> {/each} 

وضع الدولة


يمكن تحديد القيمة الجديدة للتخزين القابل للتسجيل عن طريق استدعاء الأسلوب set ، والذي يغير الحالة وفقًا للقيمة التي تم تمريرها:


 const todos = writable(initialState); function removeAll() { todos.set([]); } 

تحديث الحالة


لتحديث وحدة التخزين (في حالتنا ، todos ) ، بناءً على حالتها الحالية ، تحتاج إلى استدعاء طريقة update وتمريرها وظيفة رد اتصال ستعيد الحالة الجديدة للتخزين.


نعيد كتابة وظيفة onDone التي أنشأناها سابقًا:


 function onDone(event) { const name = event.detail; todos.update((state) => { return state.map((todo) => { return todo.name === name ? {...todo, done: true} : todo; }); }); } 

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


 // todos.store.js export function markTodoAsDone(name) { const updateFn = (state) => { return state.map((todo) => { return todo.name === name ? {...todo, done: true} : todo; }); }); todos.update(updateFn); } // App.svelte import { markTodoAsDone } from './todos.store'; function onDone(event) { const name = event.detail; markTodoAsDone(name); } 

تغيير حالة الاشتراك


لمعرفة أن القيمة في المستودع قد تغيرت ، يمكنك استخدام طريقة subscribe . ضع في اعتبارك أن المستودع ليس كائنًا observable ، ولكنه يوفر واجهة مشابهة.


 const subscription = todos.subscribe(console.log); subscription(); //     

المتغيرات الظاهرة


إذا تسبب هذا الجزء في إثارة أكبر لك ، فأسرعت لإرضاء أنه منذ وقت ليس ببعيد ، تمت إضافة دعم RxJS إلى Svelte وتم تقديم Observable for ECMAScript.


كمطور Angular ، اعتدت بالفعل على العمل مع البرمجة التفاعلية ، وسيكون عدم وجود نظير توجيه غير متزامن غير مريح للغاية. ولكن فاجأتني Svelte هنا أيضًا.


دعونا نلقي نظرة على مثال لكيفية عمل هذه الأدوات معًا: عرض قائمة من المستودعات على Github ، التي عثر عليها بالكلمة الرئيسية "Svelte" .


يمكنك نسخ الكود أدناه وتشغيله مباشرة في REPL :


 <script> import rx from "https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"; const { pluck, startWith } = rx.operators; const ajax = rx.ajax.ajax; const URL = `https://api.github.com/search/repositories?q=Svelte`; const repos$ = ajax(URL).pipe( pluck("response"), pluck("items"), startWith([]) ); </script> {#each $repos$ as repo} <div> <a href="{repo.url}">{repo.name}</a> </div> {/each} <!--   Angular: <div *ngFor="let repo of (repos$ | async)> <a [attr.href]="{{ repo.url }}">{{ repo.name }}</a> </div> --> 

فقط أضف رمز $ إلى اسم المتغير الذي يمكن ملاحظته repos$ وسيعرض Svelte محتوياته تلقائيًا.


قائمة أمنياتي ل Svelte


دعم الطباع


بصفتي متحمسًا لـ Typescript ، لا يسعني إلا أن أتمنى إمكانية استخدام أنواع في Svelte. أنا معتاد على ذلك أحيانًا ما يتم ترحيلي وترتيب أنواع في الكود الخاص بي ، والتي يجب علي إزالتها بعد ذلك. آمل حقًا أن تضيف Svelte قريبًا دعم Typescript. أعتقد أن هذا العنصر سيكون في قائمة الأمنيات لأي شخص يريد استخدام Svelte مع تجربة Angular.


الاتفاقيات والمبادئ التوجيهية


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


دعم المجتمع


Svelte هو مشروع فخم ، مع زيادة جهد المجتمع في كتابة حزم الطرف الثالث ، والأدلة ، ومقالات المدونات ، وأكثر من ذلك ، يمكن أن ينطلق ويصبح أداة معروفة في عالم تطوير Frontend المذهل الذي لدينا اليوم.


في الختام


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


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


في قناة Telegram باللغة الروسية @ sveltejs ، ستجد بالتأكيد مطورين يتمتعون بخبرة في مختلف الأطر وعلى استعداد لمشاركة قصصهم وأفكارهم ونصائحهم بشأن Svelte.

Source: https://habr.com/ru/post/ar467091/


All Articles