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

ميزات أطر العمل الرئيسية في تطوير الويب من طرف العميل


Ola Abbas

يملك كل إطار عمل جافاسكربت رئيسي نهجًا مختلف لتحديث نموذج كائن المستند 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.

اقرأ أيضًا


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...