يملك كل إطار عمل جافاسكربت رئيسي نهجًا مختلف لتحديث نموذج كائن المستند DOM، ومعالجة أحداث المتصفح، وتوفير تجربة مطوِّر ممتعة، إذ سنستكشف في هذا المقال الميزات الرئيسية لأطر عمل "الأربعة الكبار"، وكيفية عمل هذه الأطر، والاختلافات بينها.
- المتطلبات الأساسية: الإلمام بأساسيات لغات HTML وCSS وجافاسكربت.
- الهدف: فهم ميزات شيفرة أطر العمل Frameworks الرئيسية.
لغات المجال المحدد Domain-specific Languages
سنشغّل جميع أطر العمل التي سنناقشها في هذا المقال باستخدام لغة جافاسكربت، إذ ستتيح جميعها استخدام لغات المجال المحدد Domain-specific Languages -أو DSLs اختصارًا- لبناء التطبيقات، كما تستخدم مكتبة React صيغة JSX لكتابة مكوناتها، في حين يستخدِم إطار عمل Ember لغة Handlebars، كما تعرف هذه اللغات كيفية قراءة متغيرات البيانات على عكس لغة HTML، ويمكن استخدام هذه البيانات لتبسيط عملية كتابة واجهة المستخدِم؛ أما تطبيقات Angular، فتستخدِم لغة TypeScript التي لا تهتم بكتابة واجهات المستخدِم ولكنها لغة مجال محدد، وتختلف كثيرًا عن لغة جافاسكربت الصرفة Vanilla JavaScript.
لا يستطيع المتصفح قراءة لغات DSL مباشرةً، لذلك يجب تحويلها إلى لغة جافاسكربت أو HTML أولًا، إذ يُعَدّ التحويل خطوةً إضافيةً في عملية التطوير، ولكن تتضمن أدوات إطار العمل الأدوات المطلوبة لمعالجة هذه الخطوة، أو يمكن تعديلها لتضمين هذه الخطوة، كما يمكن إنشاء تطبيقات إطار عمل دون استخدام هذه اللغات، لكن سيبسّط استخدامها عملية التطوير ويسهّل العثور على المساعدة من مجتمعات تلك الأطر.
صيغة JSX
يرمز الاختصار JSX إلى لغتي جافاسكربت و XML، ويُعَدّ امتدادًا للغة جافاسكربت، إذ يضيف صيغةً تشبه لغة HTML إلى بيئة جافاسكربت، وقد اخترع فريق React صيغة JSX لاستخدامها في تطبيقات React، ولكن يمكن استخدامها لتطوير تطبيقات أخرى مثل تطبيقات Vue، وإليك مثال بسيط لصيغة JSX:
const subject = "World"; const header = ( <header> <h1>Hello, {subject}!</h1> </header> );
يمثِّل التعبير السابق عنصر <header>
في لغة HTML وبداخله عنصر <h1>
، إذ تخبر الأقواس المعقوصة حول subject
في السطر الرابع التطبيق بقراءة قيمة الثابت subject
وإدخاله في العنصر <h1>
، في حين ستُصرَّف صيغة JSX من جزء الشيفرة السابق عند استخدامها مع إطار عمل React إلى ما يلي:
var subject = "World"; var header = React.createElement("header", null, React.createElement("h1", null, "Hello, ", subject, "!") );
سينتج جزء الشيفرة السابق ما يلي في لغة HTML عندما يصيّره المتصفح في النهاية:
<header> <h1>Hello, World!</h1> </header>
لغة Handlebars
لا تُعَدّ لغة القوالب Handlebars لغةً خاصةً بتطبيقات Ember، ولكنها تُستخدَم بكثرة معها، إذ تشبه شيفرة Handlebars لغة HTML، ولكن لديها خيار سحب البيانات من مكان آخر، إذ يمكن استخدام هذه البيانات للتأثير على ملفات HTML التي يبنيها التطبيق في النهاية، كما تستخدِم لغة Handlebars -مثل صيغة JSX- الأقواس المعقوصة لحقن قيمة متغير، ولكنها تستخدِم زوجًا مزدوجًا من الأقواس المعقوصة بدلًا من زوج واحد، وإليك قالب Handlebars التالي:
<header> <h1>Hello, {{subject}}!</h1> </header>
والبيانات التالية:
{ subject: "World" }
ستبني لغة Handlebars جزء HTML التالي:
<header> <h1>Hello, World!</h1> </header>
لغة TypeScript
تُعَدّ لغة TypeScript مجموعةً شاملةً من جافاسكربت، أي أنها توسّعها، إذ تُعَدّ كل شيفرات جافاسكربت صالحةً للغة TypeScript، ولكن العكس ليس صحيحًا، كما تُعَدّ لغة TypeScript مفيدةً للصرامة التي تسمح للمطورين بفرضها على شيفرتهم البرمجية مثل دالة add()
التي تأخذ الأعداد الصحيحة a
وb
وتعيد ناتج جمعهما، ويمكن كتابة هذه الدالة في لغة جافاسكربت على النحو التالي:
function add(a, b) { return a + b; }
قد تكون هذه الشيفرة بسيطةً جدًا بالنسبة لشخص اعتاد على استخدام جافاسكربت، ولكنها يمكن أن تكون أوضح، إذ تتيح لنا لغة جافاسكربت استخدام المعامل +
لربط السلاسل مع بعضها بعضًا، لذلك ستعمل هذه الدالة أيضًا إذا كان a
وb
عبارة عن سلاسل نصية Strings، وبالتالي قد لا تمنحك النتيجة التي تتوقعها، فإذا أردنا السماح فقط بتمرير الأعداد إلى هذه الدالة، فستجعل لغة TypeScript ذلك ممكنًا كما يلي:
function add(a: number, b: number) { return a + b; }
يخبر النوع : number
المكتوب بعد كل معامِل في لغة TypeScript أنّ كلا المعامِلَين a
وb
يجب أن يكونا عددَين، فإذا أردنا استخدام هذه الدالة وتمرير القيمة '2'
إليها بوصفها وسيطًا، فستعطي لغة TypeScript خطأً أثناء التصريف Compilation، وبالتالي سنضطر إلى إصلاح هذا الخطأ، كما يمكننا كتابة شيفرة جافاسكربت الخاصة بنا والتي تعطينا هذه الأخطاء، إلا أنها ستجعل شيفرتنا البرمجية أكثر تفصيلًا، إذ يمكن أن يكون السماح للغة TypeScript بمعالجة مثل هذه الفحوصات نيابةً عنا أمرًا منطقيًا.
كتابة المكونات
تحتوي معظم أطر العمل على نموذج مكونات، إذ يمكن كتابة مكونات React باستخدام صيغة JSX، ومكونات Ember باستخدام لغة Handlebars، ومكونات Angular وVue باستخدام صيغة القوالب التي توسّع لغة HTML قليلًا، كما توفِّر مكونات كل إطار عمل -بغض النظر عن كيفية كتابة المكونات- طريقةً لوصف الخاصيات الخارجية التي قد تحتاجها، والحالة الداخلية التي يجب أن يديرها المكوِّن، والأحداث التي يمكن للمستخدِم أن أن يتفاعل بها مع المكوِّن، كما سنعطي في هذا المقال أمثلةً من مقتطفات شيفرة React والتي ستُكتَب باستخدام صيغة JSX.
الخاصيات Properties
تُعَدّ الخاصيات Properties -أو props اختصارًا- بيانات خارجية يحتاجها المكوِّن من أجل تصييرها Render، ولنفترض أنك تنشئ موقعًا إلكترونيًا لمجلة على الإنترنت، وتحتاج إلى التأكُّد من أن كل كاتب مساهم يُنسَب له عمله، فيمكنك إنشاء مكوِّن AuthorCredit
لكل مقال، إذ يحتاج هذا المكوِّن إلى عرض صورة شخصية للمؤلف وسطر قصير يحتوي على بعض المعلومات عنه، لذلك يحتاج المكوِّن AuthorCredit
إلى قبول بعض الخاصيات من أجل معرفة الصورة المراد تصييرها والسطر القصير المطلوب طباعته، إذ يمكن أن يبدو تمثيل React للمكوِّن AuthorCredit
كما يلي:
function AuthorCredit(props) { return ( <figure> <img src={props.src} alt={props.alt} /> <figcaption>{props.byline}</figcaption> </figure> ); }
تمثِّل {props.src} و{props.alt} و{props.byline} المكان الذي ستُدرَج فيه الخاصيات ضمن المكوِّن، إذ يمكن تصيير هذا المكوِّن من خلال كتابة الشيفرة التالية في المكان الذي نريده، والذي سيكون على الأرجح ضمن مكوِّن آخر:
<AuthorCredit src="./assets/zelda.png" alt="Portrait of Zelda Schiff" byline="Zelda Schiff is editor-in-chief of the Library Times." />
مما يؤدي في النهاية إلى تصيير عنصر <figure>
التالي في المتصفح مع بنيته المحدَّدة في المكوِّن AuthorCredit
، ومحتواه المحدَّد في الخاصيات المدرجة في استدعاء المكوِّن AuthorCredit
:
<figure> <img src="assets/zelda.png" alt="Portrait of Zelda Schiff" > <figcaption> Zelda Schiff is editor-in-chief of the Library Times. </figcaption> </figure>
الحالة State
يُعَدّ وجود آلية قوية للتعامل مع الحالة مفتاحًا لإطار عمل فعّال، وقد يحتوي كل مكوِّن على بيانات يجب التحكم بحالتها، إذ ستستمر هذه الحالة بطريقة ما طالما أنّ المكوِّن قيد الاستخدام، ويمكن استخدام الحالة مثل الخاصيات للتأثير على كيفية تصيير المكوِّن، ولنفترض مثلًا وجود زر يحسب عدد مرات النقر فوقه، إذ يجب أن يكون هذا المكوِّن مسؤولًا عن تتبّع حالة العد count
الخاصة به، ويمكن كتابته كما يلي:
function CounterButton() { const [count] = useState(0); return ( <button>Clicked {count} times</button> ); }
يُعَدّ useState()
خطاف React الذي سيتتبع قيمة بيانات أولية أثناء تحديثها عند إعطائه تلك القيمة، وستُصيَّر الشيفرة بدايةً كما يلي في المتصفح:
<button>Clicked 0 times</button>
يتتبّع استدعاء الخطاف useState()
الحالة count
بطريقة قوية عبر التطبيق دون الحاجة إلى كتابة شيفرة لتنفيذ ذلك بنفسك.
الأحداث Events
تحتاج المكونات إلى طرق للاستجابة لأحداث المتصفح من أجل أن تكون تفاعلية، وبالتالي ستتمكن تطبيقاتنا من الاستجابة للمستخدِمين، إذ يوفّر كل إطار من أطر العمل صيغته الخاصة للاستماع إلى أحداث المتصفح، والتي تشير إلى أسماء أحداث المتصفح الأصيلة المكافِئة، إذ يتطلب الاستماع إلى حدث النقر click
خاصيةً خاصةً هي onClick
في React، ولنحدّث شيفرة CounterButton
السابقة للسماح لها بحساب عدد النقرات كما يلي:
function CounterButton() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}>Clicked {count} times</button> ); }
استخدمنا دالة useState()
إضافية لإنشاء دالة setCount()
خاصة يمكن استدعاؤها لتحديث قيمة count
، إذ نستدعي هذه الدالة في السطر الرابع، ونضبط قيمة count
على قيمتها الحالية مع إضافة 1 إليها.
مكونات التنسيق Styling components
يوفِّر كل إطار من أطر العمل طريقةً لتحديد تنسيقات لمكوناتك أو للتطبيق كله، إذ توفِّر جميعها طرقًا متعددةً لتعريف تنسيقات المكوِّن على الرغم من اختلاف نهج كل إطار عن الآخر، ويمكنك تصميم تطبيقات إطار العمل باستخدام Sass أو Less، أو تحويل Transpile ملفات تنسيقات CSS باستخدام PostCSS مع إضافة بعض الوحدات المساعِدة.
التعامل مع الاعتماديات
توفِّر جميع الأطر الرئيسية آليات للتعامل مع الاعتماديات Dependencies باستخدام مكوِّنات ضمن مكوّنات أخرى وبمستويات هرمية متعددة في بعض الأحيان، وستختلف آليات هذه الإطارات عن بعضها بعضًا، ولكن النتيجة النهائية هي نفسها كما هو الحال مع الميزات الأخرى، كما تميل المكوّنات إلى استيراد مكوّنات في مكوّنات أخرى باستخدام صيغة وحدة جافاسكربت المعيارية أو شيء آخر مشابه.
مكونات ضمن مكونات أخرى
تتمثَّل إحدى الفوائد الرئيسية لبنية واجهة المستخدِم القائمة على المكوّنات في أنه يمكن تكوين المكوّنات مع بعضها بعضًا، إذ يمكنك استخدام مكونات ضمن مكونات أخرى لبناء تطبيق ويب مثل كتابة وسوم HTML ضمن بعضها بعضًا لإنشاء موقع ويب، كما يتيح لك كل إطار عمل بكتابة مكوّنات تستخدِم وتعتمد على مكوّنات أخرى، كما يمكن استخدام مكوِّن React الذي هو AuthorCredit
ضمن المكوِّن Article
مثلًا، وهذا يعني حاجة المكوِّن Article
إلى استيراد المكوِّن AuthorCredit
.
import AuthorCredit from "./components/AuthorCredit";
يمكن بعد ذلك استخدام المكوِّن AuthorCredit
ضمن المكوِّن Article
كما يلي:
... <AuthorCredit /> …
حقن الاعتماديات
تشتمل التطبيقات الواقعية على بنى مكونات ذات مستويات متعددة من التداخل Nesting في أغلب الأحيان، وقد يحتاج مكوِّن AuthorCredit
المتداخل بعمق في العديد من المستويات لسبب ما إلى بيانات من المستوى الجذر لتطبيقنا، ولنفترض تنظيم موقع المجلة الذي نبنيه على النحو التالي:
<App> <Home> <Article> <AuthorCredit {/* props */} /> </Article> </Home> </App>
يحتوي المكوِّن App
على البيانات التي يحتاجها المكوِّن AuthorCredit
، كما يمكننا إعادة كتابة المكوِّن Home
وArticle
لمعرفة كيفية تمرير الخاصيات، ولكن قد يكون ذلك مملًا إذا كان هناك العديد من المستويات بين أصل ووجهة البيانات، كما قد يؤثر ذلك على الأداء، فلا يستخدِم المكوِّنان Home
وArticle
صورة المؤلف أو السطر القصير الذي يعطي معلومات مختصَرةً عن المؤلف، ولكن إذا أردنا الحصول على هذه المعلومات في المكوِّن AuthorCredit
، فينحتاج إلى تغيير المكوِّنَين Home
وArticle
لإضافتها.
تُسمَّى مشكلة تمرير البيانات عبر العديد من طبقات المكوّنات بتمرير الخاصيات Prop Drilling التي لا تُعَدّ مثاليةً للتطبيقات الكبيرة، إذ يمكن التحايل على هذه المشكلة من خلال توفير أطر العمل وظيفة تُعرَف باسم حقن الاعتمادية Dependency Injection، وهي طريقة لإعطاء بيانات معينة مباشرةً إلى المكوّنات التي تحتاجها دون تمريرها عبر المستويات المتداخلة، إذ ينفّذ كل إطار عمل عملية حقن الاعتمادية تحت اسم مختلف وبطريقة مختلفة، ولكن التأثير هو نفسه في النهاية.
يسمّي إطار العمل Angular هذه العملية حقن الاعتمادية، في حين يمتلك إطار العمل Vue توابع المكوّنات provide()
وinject()
؛ أما React، فيحتوي على واجهة برمجة تطبيقات السياق Context API، بينما يشارك إطار عمل Ember الحالة من خلال خدمات.
دورة الحياة Life Cycle
تُعَدّ دورة حياة المكوّن في سياق إطار العمل مجموعةً من المراحل التي يمر بها المكوِّن من وقت إلحاقه بنموذج DOM ثم تصييره بواسطة المتصفح -والذي يدعى بالتركيب Mounting في أغلب الأحيان- إلى وقت إزالته من نموذج DOM -والذي يطلَق عليه التفكيك Unmounting في أغلب الأحيان، كما يسمّي كل إطار عمل مراحل دورة الحياة هذه بطريقة مختلفة، ولا تمنح جميعها المطورين الوصول إلى المراحل نفسها، كما تتبع جميع الأطر النموذج العام نفسه، إذ تسمح للمطورين بتنفيذ إجراءات معينة عند تركيب Mount المكوِّن، وعند تصييره، وعند تفكيكه Unmount، وفي عدة مراحل بينها.
تُعَدّ مرحلة التصيير Render المرحلة الأهم، لأنها تتكرر عندما يتفاعل المستخدِم مع تطبيقك، وتُشغَّل في كل مرة يحتاج فيها المتصفح إلى تصيير شيء جديد، سواءً كانت هذه المعلومات الجديدة إضافةً إلى ما هو موجود في المتصفح أو حذفه أو تعديله، كما يمكنك الاطلاع على هذا الرسم البياني لدورة حياة مكون React الذي يوضِّح هذا المفهوم.
تصيير العناصر
تتخِذ أطر العمل أساليبًا مختلفةً ولكنها متشابهة لتصيير التطبيقات، وتتعقّب جميعها الإصدار الحالي المُصيَّر من DOM في متصفحك، كما تتخذ كل منها قرارات مختلفةً قليلًا حول كيفية تغيير نموذج DOM مثل إعادة تصيير المكوّنات في تطبيقك، وبما أنّ أطر العمل تتخِذ هذه القرارات نيابةً عنك، فهذا يعني أنك لا تتفاعل مع نموذج DOM بنفسك، كما يُعَدّ هذا التجريد البعيد عن نموذج DOM أكثر تعقيدًا واستهلاكًا للذاكرة من تحديثه بنفسك، ولكن لا يمكن لأطر العمل بدونه السماح لك بالبرمجة بالطريقة التصريحية المعروفة بها.
يُعَدّ نموذج DOM الافتراضي Virtual DOM نهجًا يمكن من خلاله تخزين معلومات حول نموذج DOM في متصفحك ضمن ذاكرة جافاسكربت، إذ يحدّث تطبيقك هذه النسخة من DOM، ثم يوازنها مع DOM الحقيقي المصيَّر لمستخدِميك فعليًا لتحديد ما سيُصيَّر، كما ينشئ التطبيق اختلافًا Diff لموازنة الاختلافات بين DOM الافتراضي المُحدَّث و DOM المُصيَّر حاليًا، إذ يُستخدَم هذا الاختلاف لتطبيق التحديثات على نموذج DOM الحقيقي، كما يستخدِم كل من React وVue نموذج DOM الافتراضي، لكنهما لا يطبِّقان المنطق نفسه بالضبط عند تطبيق الاختلاف Diffing أو التصيير Rendering، ويمكنك قراءة المزيد عن DOM الافتراضي في توثيق React على موسوعة حسوب.
يشبه نموذج DOM التزايدي Incremental DOM نموذج DOM الافتراضي Virtual DOM في أنه ينشئ اختلافًا في نموذج DOM لتحديد ما سيُصيَّر، إلا أنه يختلف في عدم إنشائه نسخةً كاملةً من DOM في ذاكرة جافاسكربت، وهو يتجاهل أجزاء DOM التي لا تحتاج إلى تغيير، فإطار العمل Angular هو الإطار الوحيد الذي ناقشناه حتى الآن والذي يستخدِم نموذج DOM التزايدي، كما يمكنك قراءة المزيد حول نموذج DOM التزايدي على مدونة Auth0.
آلة Glimmer الافتراضية خاصة بإطار عمل Ember، ولا تُعَدّ نموذج DOM افتراضي أو DOM تزايدي، وإنما هي عملية منفصلة يمكن من خلالها تحويل قوالب Ember إلى نوع من شيفرة ثنائية Byte Code تكون أسهل وأسرع في القراءة من جافاسكربت.
التوجيه Routing
يُعَدّ التوجيه جزءًا مهمًا من تجربة الويب، إذ يوفِّر كل إطار عمل مكتبةً أو أكثر بحيث تساعد المطورين على تنفيذ التوجيه من جانب العميل في تطبيقاتهم، لتجنّب تجربة معطَّلة في التطبيقات المعقدة ذات المشاهدات الكثيرة.
الاختبار Testing
تستفيد جميع التطبيقات من تغطية الاختبار التي تضمن استمرار برنامجك في التصرف بالطريقة التي تتوقعها، إذ يوفر النظام المجتمعي لكل إطار عمل الأدوات التي تسهّل كتابة الاختبارات، ولا تُضمَّن أدوات الاختبار في الأطر نفسها، ولكن تمنحك أدوات واجهة سطر الأوامر المُستخدَمة لإنشاء تطبيقات إطار العمل، الوصول إلى أدوات الاختبار المناسبة، إذ يحتوي كل إطار على أدوات واسعة النطاق في نظامه المجتمعي مع إمكانات اختبار الوحدة والتكامل على حد سواء.
تُعَدّ مكتبة الاختبار Testing Library مجموعةً من أدوات الاختبار المساعِدة التي تحتوي على أدوات للعديد من بيئات جافاسكربت بما في ذلك React وVue وAngular، ويغطي توثيق Ember اختبار تطبيقاته، وإليك اختبار سريع للمكوِن CounterButton
مكتوب بمساعَدة مكتبة اختبار React، إذ يختبر هذا الاختبار عددًا من الأشياء مثل وجود الزر وما إذا كان الزر يعرض النص الصحيح بعد النقر عليه 0 و1 و2 مرة:
import React from "react"; import { render, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; import CounterButton from "./CounterButton"; it("renders a semantic with an initial state of 0", () => { const { getByRole } = render(<CounterButton />); const btn = getByRole("button"); expect(btn).toBeInTheDocument(); expect(btn).toHaveTextContent("Clicked 0 times"); }); it("Increments the count when clicked", () => { const { getByRole } = render(<CounterButton />); const btn = getByRole("button"); fireEvent.click(btn); expect(btn).toHaveTextContent("Clicked 1 times"); fireEvent.click(btn); expect(btn).toHaveTextContent("Clicked 2 times"); });
الخلاصة
يجب أن يكون لديك الآن مزيدًا من الأفكار حول اللغات والميزات والأدوات الفعلية التي ستستخدِمها أثناء إنشاء التطبيقات باستخدام أطر العمل، ولا بدّ أنك متحمس للبدء بكتابة الشيفرة، وهذا ما ستفعله لاحقًا، ولكن يمكنك الآن اختيار إطار العمل الذي ترغب في بدء تعلمه أولًا مثل:
- React
- Ember
- Vue
- Svelte
- Angular
ترجمة -وبتصرُّف- للمقال Framework main features.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.