Skip to content

جوهر أُطُر العمل الأمامية

💡 دليل التعلم: هذه المقالة ستُجيب عن سؤال جوهري — أُطُر العمل الأمامية (Vue وReact وSvelte وغيرها) ماذا تفعل بالضبط؟ إذا كنت قد تعلمت فقط HTML وCSS وبعض JavaScript، فلا مشكلة على الإطلاق، سنبدأ من الصفر.

قبل البدء، تأكد من أنك تعرف المفهومين الأساسيين التاليين. إذا لم تكن متأكدًا، يمكنك مراجعة الفصول المخصصة لهما:

  • HTML: الهيكل العظمي لصفحة الويب، يُحدد العناصر الموجودة على الصفحة (عناوين، فقرات، أزرار، صور...). راجع تخطيط HTML وCSS.
  • JavaScript: لغة البرمجة التي تجعل صفحة الويب "تتحرك"، يمكنها تعديل محتوى الصفحة والاستجابة لعمليات المستخدم. راجع دليل JavaScript المتعمق.

هناك أيضًا مفهوم سيظهر بشكل متكرر لاحقًا، سنشرحه بالكامل هنا أولًا.

ما هو DOM؟

DOM هو اختصار لـ Document Object Model، أي "نموذج كائن المستند".

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

كل عقدة (Node) على الشجرة تقابل علامة HTML. علاقات التعشيق بين العلامات تتحول في شجرة DOM إلى علاقات عقدة أب وعقدة ابن.

👇 جرّب بنفسك: حرّك الماوس فوق كود HTML على اليسار، وستتوهج العقدة المقابلة في شجرة DOM على اليمين. والعكس صحيح أيضًا. كل سطر من علامات HTML يقابل عقدة واحدة في شجرة DOM.

HTML → DOM treeHow the browser understands your HTML
HTML code you write
1<html>
2<body>
3<h1>My cart</h1>
4<p>3 products total</p>
5<ul>
6<li>Headphones</li>
7<li>Keyboard</li>
8<li>Mouse</li>
9</ul>
10<button>Checkout</button>
11</body>
12</html>
Browser parses
DOM tree generated by the browser
html
└─body
└─h1"My cart"
└─p"3 products total"
└─ul
└─li"Headphones"
└─li"Keyboard"
└─li"Mouse"
└─button"Checkout"
📄
NodeEvery box in the DOM tree is a node. Each HTML tag, such as <h1> or <p>, maps to one node.
🌳
Parent-child relationshipWhen one tag is nested inside another, the DOM tree represents that as a parent-child relationship.
✏️
DOM operationJavaScript can add, remove, or change nodes in the DOM tree. After a node changes, the browser recalculates layout and paints the page again.
Key concept:The DOM is a tree maintained by the browser in memory. It corresponds to the HTML you wrote. JavaScript changes this DOM tree, and the browser updates the screen from that changed tree.

لماذا يجب أن تفهم DOM؟ لأن طريقة JavaScript في تعديل الصفحة هي التعامل مع شجرة DOM هذه — إضافة عقد، حذف عقد، تعديل محتوى العقد. والعمل الأساسي الذي تقوم به أُطُر العمل الأمامية هو أتمتة عمليات DOM هذه نيابةً عنك. سنذكر DOM مرارًا لاحقًا، وفهمه هو الأساس لفهم مبادئ أُطُر العمل.


0. مقدمة: ما هو "إطار العمل الأمامي"؟

دعنا أولًا نشرح كلمة "إطار العمل". في البرمجة، Framework (إطار العمل) هو مجموعة من الكود والقواعد المكتوبة مسبقًا، والتي تحدد كيف يجب تنظيم الكود الخاص بك وكيف يجب أن يعمل. أنت تكتب الكود بطريقته، وهو يتولى عنك الكثير من العمل المتكرر والمعقد في المستوى الأدنى.

إطار العمل الأمامي، هو إطار عمل متخصص في مساعدتك على بناء واجهات صفحات الويب. الأكثر شيوعًا حاليًا هي Vue وReact وSvelte وAngular.

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

Problem
  • When data changes, manual DOM updates are easy to miss.
  • The more complex a page becomes, the more places need synchronization.
  • In team work, scattered DOM operations become hard to maintain.
Root cause
  • The browser does not know the relationship between data and UI.
  • Native DOM APIs are low-level operations, not automatic UI synchronization.
  • Developers are forced to act as manual synchronizers.
Framework solution
  • Build a mapping from data to UI: UI = f(State).
  • Detect data changes automatically with reactivity.
  • Compute minimal DOM updates with virtual DOM or compiler optimization.

بعد ذلك سنشرح خطوة بخطوة، بدءًا من المسألة الأساسية.


1. المشكلة الجوهرية: البيانات تغيرت، ماذا عن الواجهة؟

1.1 أوضح أولًا ما هي "البيانات" وما هي "الواجهة"

في أي تطبيق ويب، يوجد شيئان معًا في نفس الوقت:

  • البيانات (Data / State): المعلومات المخزنة داخل البرنامج. مثل "في سلة التسوق 3 منتجات" و"اسم المستخدم هو أحمد" و"التبويب المحدد حاليًا هو الثاني". هذه البيانات مخزنة في متغيرات JavaScript، والمستخدم لا يراها.
  • الواجهة (UI): ما يراه المستخدم على الشاشة. مثل عرض "سلة التسوق(3)" على الصفحة، وعرض "مرحبًا، أحمد"، والتبويب الثاني مُظلل. هذه هي التأثيرات البصرية التي تقدمها عناصر HTML.

توجد علاقة تطابق بين البيانات والواجهة: البيانات هي "3 منتجات"، والواجهة يجب أن تعرض "3". إذا أصبحت البيانات "4 منتجات"، يجب أن تتغير الواجهة أيضًا إلى "4".

المشكلة هي: من المسؤول عن عملية "التغير معًا" هذه؟

👇 جرّب النقر: انقر على زر "إضافة منتج"، ولاحظ أن البيانات (على اليسار) قد تغيرت، لكن الواجهة (على اليمين) لم تتبع التحديث — أصبحت "منفصلة" بينهما. انقر "مزامنة الواجهة" للإصلاح يدويًا.

Data (JavaScript variable)
Product count0
Total price¥0
StatusNormal
✅ Synced
UI (what users see)
Cart0 item(s)
Total price¥0
StatusNormal
Core problem:Without a framework, changing data does not automatically update the UI. You must write code to update the interface yourself. If you forget, users see stale or wrong information.

1.2 لماذا عندما يتغير متغير JavaScript، لا تتحدث الواجهة تلقائيًا؟

هذه أكثر نقطة تُربك المبتدئين، سنشرح المبدأ الأساسي خطوة بخطوة.

في JavaScript، المتغير هو ببساطة مساحة في الذاكرة تُستخدم لتخزين البيانات. عندما تنفذ count = count + 1، ما يفعله محرك JavaScript بسيط جدًا: يغيّر القيمة في موقع count في الذاكرة من 3 إلى 4. بعد هذه الخطوة ينتهي كل شيء، ولن يحدث أي شيء آخر.

أما المحتوى المعروض على الصفحة (مثل عقدة DOM <span>3</span>) فمخزن في مساحة ذاكرة مختلفة تمامًا. عندما يُعدّل محرك JavaScript المتغير، فإنه لا يعرف أصلًا أن هناك عقدة DOM على الصفحة تعرض قيمة هذا المتغير، وليست هناك أي آلية تجعله يتحقق.

لذلك السبب الجوهري هو: متغيرات JavaScript وعُقد DOM كتلتا ذاكرة مستقلتان، لا توجد بينهما أي آلية ربط تلقائي. تعديل المتغير يغيّر فقط الذاكرة التي يحتلها المتغير، بينما الذاكرة التي تحتلها عقدة DOM لا تتأثر إطلاقًا.

javascript
let count = 3

// على الصفحة عقدة DOM تعرض قيمة count:
// <span id="counter">3</span>

count = 4
// ماذا فعل محرك JavaScript؟
//   → غيّر قيمة المتغير count في الذاكرة من 3 إلى 4
//   → انتهى. فقط.
// ما زال <span> على الصفحة يعرض "3"

إذا أردت أن ما يظهر على الصفحة يتغير أيضًا إلى "4"، يجب عليك كتابة كود إضافي، والعثور يدويًا على عقدة DOM تلك، ثم تعديل محتواها:

javascript
count = 4  // الخطوة 1: تغيير المتغير

// الخطوة 2: يجب أن تكتب بنفسك — ابحث عن عقدة DOM وغيّر نصها إلى القيمة الجديدة
document.getElementById('counter').textContent = count

إذا كان على الصفحة 5 أماكن تعرض قيمة count (عدد سلة التسوق، قائمة المنتجات، السعر الإجمالي، المجموع الفرعي، رسالة الحالة)، ستحتاج إلى كتابة 5 أجزاء من هذا الكود. إذا فاتك أي جزء، فذلك المكان سيظل يعرض القيمة القديمة، والمستخدم سيرى معلومات خاطئة.

1.3 ماذا يفعل إطار العمل؟ خطوتان لبناء اتصال تلقائي

يستطيع إطار العمل المزامنة التلقائية بفضل تنسيق خطوتين — لا غنى عن إحداهما.

الخطوة الأولى: أنت "تُسجّل" في القالب الأماكن التي تريد عرض المتغير فيها

في قالب HTML الخاص بإطار العمل، تستخدم صيغة مثل لتحديد "هذا المكان يريد عرض قيمة count":

html
<!-- قالب Vue -->
<span>سلة التسوق: {{ count }} منتجات</span>    <!-- الموقع أ: أريد عرض count -->
<span>السعر الإجمالي: ¥{{ count * 99 }}</span>   <!-- الموقع ب: أنا أيضًا أستخدم count -->
<span>{{ count > 5 ? 'كثير جدًا' : 'طبيعي' }}</span>  <!-- الموقع ج: أنا أيضًا أستخدم count -->

عندما يعرض إطار العمل الصفحة لأول مرة، يُسجّل "علاقة التسجيل" هذه: المواقع أ وب وج تعتمد جميعها على count.

الخطوة الثانية: يراقب إطار العمل المتغير، وعندما يتغير يفحص جدول التسجيل ويُحدّث تلقائيًا

يستخدم إطار العمل آلية Proxy (الوكيل) المدمجة في JavaScript ليُغلف متغيرك، ويحوّله إلى "متغير مراقَب". عندما تُعدّل هذا المتغير، يقوم Proxy بأن يفعل شيئًا إضافيًا بصمت أثناء الإسناد: يُخطر إطار العمل بأن "count قد تغيّر". بعد أن يتلقى إطار العمل الإخطار، يذهب لفحص جدول التسجيل من الخطوة الأولى، ويُحدّث المواقع الثلاثة أ وب وج.

JS الأصلي:
  أنت تكتب HTML → <span id="counter">3</span> (بدون أي اتصال مع المتغير)
  أنت تغيّر المتغير → count = 4 → انتهى، الواجهة بلا أي استجابة
  أنت تُكمّل يدويًا → document.getElementById('counter').textContent = 4 → الواجهة تتحدث فقط

إطار العمل Vue:
  أنت تكتب القالب → <span>{{ count }}</span> (إطار العمل يتذكر: هذا المكان يعتمد على count)
  أنت تغيّر المتغير → count = 4 → Proxy يعترض → يُخطر إطار العمل → إطار العمل يفحص جدول التسجيل → يُحدّث تلقائيًا أ/ب/ج

هذا هو سبب أن "فقط إطار العمل يمكنه المزامنة تلقائيًا" — <span> في HTML الأصلي ومتغير JS ليس بينهما أي اتصال أصلًا، صيغة القالب () الخاصة بإطار العمل هي المفتاح لبناء هذا الاتصال. أنت تكتب ، فيعرف إطار العمل أن هذا المكان يجب أن يعرض count؛ وعندها فقط يستطيع إطار العمل عند تغير count أن يحدد هذا الموقع بدقة ويُحدّثه.

👇 جرّب النقر: اختر أولًا "JavaScript الأصلي"، بعد النقر على "تنفيذ" لاحظ — المتغير تغيّر لكن الواجهة ثابتة، عليك أن تُزامن كل موقع خطوة بخطوة يدويًا. ثم انتقل إلى "استخدام إطار العمل"، وانقر "تنفيذ" أيضًا — بمجرد تغير المتغير، يُكمل إطار العمل جميع الخطوات تلقائيًا، والواجهة تتبع فورًا.

What happens when a variable changes?Native JavaScript vs framework
Code you write
// Run when the button is clicked
count = count + 1
// You still have to write these lines:
document.getElementById('count')
.textContent = count
document.getElementById('total')
.textContent = count * 99
Execution flow
1
JavaScript changes the variable
count changes from 0 to 1
2
Find DOM nodes
Call document.getElementById() manually
3
Change DOM content
Set .textContent manually
UI result
Cart0 item(s)
Total price¥0
Why is it not automatic?JavaScript variables are not aware of the UI. When you run count = 4, the engine only changes the value in memory. It does not notify anyone, trigger a callback, or check where count is displayed on the page. The UI will not change unless you update the DOM yourself.

1.4 مقارنة: تأثير المزامنة اليدوية مقابل المزامنة التلقائية

بعد فهم المبدأ، دعنا نرى في سيناريو أكثر تعقيدًا قليلًا، كم هو كبير الفرق بين المزامنة اليدوية والمزامنة التلقائية.

👇 جرّب النقر: على اليسار طريقة "المزامنة اليدوية" بدون إطار عمل — كل منطقة عرض تحتاج أن تنقر زر "مزامنة" بشكل منفصل للتحديث. على اليمين طريقة "المزامنة التلقائية" مع إطار عمل — أنت فقط انقر "إضافة منتج"، وجميع مناطق العرض تتحدث تلقائيًا. جرّب أن تتعمد عدم مزامنة منطقة معينة على اليسار، وانظر ماذا سيحدث.

Manual sync / jQuery style
🔴Cart countSynced
0 item(s)
📋Product listSynced
(empty)
💰TotalSynced
¥0
⚠️StatusSynced
Normal
Misses:0
VS
Automatic sync / framework style
🔴Cart countSynced
0 item(s)
📋Product listSynced
(empty)
💰TotalSynced
¥0
⚠️StatusSynced
Normal
Misses:0
Core idea:The essential value of frontend frameworks is automatic synchronization: you change data, and the framework updates every UI location that depends on it.

هذا هو السبب الجوهري لوجود أُطُر العمل الأمامية: إضافة قدرة "الإخطار التلقائي للواجهة بالتحديث عند التعديل" إلى متغيرات JavaScript، والقضاء على الأخطاء الناتجة عن المزامنة اليدوية.


2. الفكرة الجوهرية لإطار العمل: وصف الواجهة بالبيانات

2.1 الفرق بين طريقتي الكتابة

بعد فهم قيمة "المزامنة التلقائية"، دعنا نرى كيف يُنفذ إطار العمل ذلك تحديدًا.

في حقبة ما قبل أُطُر العمل (مثل استخدام jQuery)، كان الكود يُكتب هكذا — أنت تُخبر المتصفح خطوة بخطوة ماذا يفعل:

javascript
// الخطوة 1: ابحث عن العنصر الذي يحمل id يساوي counter على الصفحة
var element = document.getElementById('counter')
// الخطوة 2: غيّر المحتوى النصي لهذا العنصر إلى القيمة الجديدة
element.textContent = '4'
// الخطوة 3: ابحث عن عنصر آخر، وغيّره أيضًا
document.getElementById('total').textContent = '¥396'
// الخطوة 4: إذا كانت الكمية أكبر من 5، يجب أيضًا تغيير رسالة الحالة...

هذه الطريقة في الكتابة تُسمى Imperative (أمري) — أنت "تأمر" المتصفح بتنفيذ العمليات خطوة بخطوة.

مع وجود إطار العمل، يصبح الكود هكذا — أنت فقط تصف "كيف يجب أن تبدو الواجهة":

html
<!-- لا يهمني كيف تُحدَّث هذه القيمة على الصفحة -->
<!-- أنا فقط أقول: هذا المكان يجب أن يعرض قيمة count -->
<span>{{ count }}</span>
<span>السعر الإجمالي: ¥{{ count * 99 }}</span>
<span v-if="count > 5">المنتجات كثيرة جدًا!</span>

هذه الطريقة في الكتابة تُسمى Declarative (تقريري) — أنت "تُصرّح" بالحالة النهائية للواجهة، أما كيفية الوصول إلى هذه الحالة فيتولاه إطار العمل بنفسه.

2.2 الصيغة الجوهرية: UI = f(State)

جميع أُطُر العمل الأمامية الحديثة — سواء Vue أو React أو Svelte — تتبع نفس الفكرة الجوهرية، والتي يمكن التعبير عنها بصيغة واحدة:

UI = f(State)

هذه الصيغة تعني:

  • State (الحالة): بيانات تطبيقك. إنها ببساطة تلك المتغيرات في JavaScript: كم عدد المنتجات في سلة التسوق، هل سجّل المستخدم الدخول، ما هي الصفحة الحالية...
  • f (الدالة): آلية العرض الخاصة بإطار العمل. إنها تعرف كيف تحوّل البيانات إلى واجهة.
  • UI (الواجهة): النتيجة النهائية التي يراها المستخدم على الشاشة.

المعنى: بمجموعة بيانات مُعطاة (State)، وبعد معالجة إطار العمل (f)، يمكن الحصول بشكل حتمي على الواجهة المقابلة (UI). البيانات تتغير، فتتغير الواجهة تبعًا لذلك. يحتاج المطور فقط إلى الاهتمام بالبيانات، ولا يحتاج إلى الاهتمام بكيفية تحديث الواجهة.

👇 جرّب النقر: عدّل البيانات (State) على اليسار، ولاحظ كيف تتغير الواجهة (UI) على اليمين تلقائيًا. هذا هو التجسيد المرئي لـ UI = f(State).

State (data)
→ f →
UI
Change State
2
Rendered result (UI)
Hello, guest!
Cart: 2 item(s)
Total: ¥198
Current theme: Light
Current State snapshot
{ "username": "(empty)", "count": 2, "darkMode": false }
Core idea:You only change State. The framework renders the corresponding UI automatically. The same data always renders the same UI: UI = f(State).

2.3 لماذا Declarative أفضل من Imperative؟

تكمن مزايا طريقة الكتابة Declarative في:

بُعد المقارنةImperative (بدون إطار عمل)Declarative (مع إطار عمل)
حجم الكودكل تحديث يحتاج كتابة كود عمليات محددتكتب القالب مرة واحدة فقط، وإطار العمل يعالج الباقي تلقائيًا
احتمال الخطأمن السهل تفويت تحديث مكان ماإطار العمل يضمن تحديث جميع الأماكن
سهولة القراءةالكود ممزوج بعمليات DOM كثيرةالكود يصف بوضوح بنية الواجهة
تكلفة الصيانةتعديل ميزة واحدة يتطلب تغيير أماكن كثيرةيكفي تعديل منطق البيانات، والواجهة تتبع تلقائيًا

ببساطة: Declarative تتيح لك التركيز على "منطق الأعمال" (كيف تتغير البيانات)، دون القلق بشأن "كيف تُحدَّث الواجهة" — هذا العمل المتكرر والمعرّض للأخطاء.


3. نظام التفاعلية: كيف يعرف إطار العمل أن البيانات تغيرت؟

3.1 ما هي "التفاعلية"؟

سابقًا ذكرنا أن "البيانات تتغير، والواجهة تُحدَّث تلقائيًا". لكن هناك مشكلة تقنية: JavaScript بحد ذاته لا يمتلك قدرة "الإخطار التلقائي للآخرين عند تعديل المتغيرات".

أنت تكتب count = 4، فيغيّر JavaScript قيمة count من 3 إلى 4 فقط، ولن يُخطر أي شخص تلقائيًا. إطار العمل يحتاج إلى آلية "لاكتشاف" أنك عدّلت البيانات.

Reactivity (التفاعلية) هي الاسم العام لهذه الآلية: عندما تتغير البيانات، يستطيع النظام الإحساس بالتغيير تلقائيًا وتنفيذ عمليات التحديث المقابلة.

3.2 ثلاث طرق تنفيذ مختلفة

تتبنى أُطُر العمل المختلفة حلولًا تقنية مختلفة لتنفيذ التفاعلية. وهذا أيضًا هو الاختلاف الأكثر جوهرية بين Vue وReact وSvelte.

الطريقة الأولى: اعتراض الوكيل (طريقة Vue)

يستخدم Vue آلية Proxy (الوكيل) المدمجة في JavaScript. يمكن لـ Proxy أن ينفذ تلقائيًا كودًا تحدده أنت عند قراءة أو تعديل خاصية من خصائص كائن.

يُغلف Vue كائن البيانات الخاص بك باستخدام Proxy. عندما تنفذ count = 4، يعترض Proxy عملية الكتابة هذه، ويُخطر Vue بأن "قيمة count قد تغيرت"، ثم يذهب Vue لتحديث جميع أجزاء الواجهة التي تستخدم count.

أنت كمطور لا تحتاج إلى فعل أي شيء إضافي — فقط أسند القيمة مباشرة، وVue يحس بالتغيير تلقائيًا.

الطريقة الثانية: الاستدعاء الصريح (طريقة React)

React لا يستخدم Proxy. بل يتطلب منك تعديل البيانات من خلال دالة متخصصة:

javascript
// طريقة كتابة React
const [count, setCount] = useState(0)

// لا يمكنك كتابة count = 4 مباشرة (React لن يشعر بذلك)
// يجب استدعاء setCount:
setCount(4)

فقط عندما تستدعي setCount() يعلم React أن البيانات قد تغيرت، ويذهب لتحديث الواجهة. إذا كتبت مباشرة count = 4، فلن يعلم React أصلًا، ولن تتحدث الواجهة.

هذه الطريقة أكثر "صراحة" — كل تغيير في البيانات تُخطر به إطار العمل بنفسك بشكل نشط، ولن تكون هناك تحديثات غير متوقعة.

الطريقة الثالثة: تحليل المُترجم (طريقة Svelte)

اتخذ Svelte مسارًا مختلفًا تمامًا. لديه مُترجم (Compiler)، وقبل تشغيل كودك، يحلل المُترجم الكود المصدري الخاص بك.

عندما يرى المُترجم أنك كتبت عبارة إسناد مثل count += 1، فإنه يُدرج تلقائيًا بعد هذا السطر كود "إخطار الواجهة بالتحديث". أي أنه عند تشغيل الكود، يكون فعل "الإخطار" قد رتّبه المُترجم مسبقًا.

يبدو كودك كإسناد JavaScript عادي، لكن في الكود المُترجم أُضيف منطق تحديث الواجهة.

👇 جرّب النقر: اختر تبويبات أُطُر العمل المختلفة، انقر "تعديل البيانات"، ولاحظ الخطوات التي يمر بها كل إطار عمل "تحت الغطاء" لإكمال اكتشاف تغير البيانات وتحديث الواجهة.

count:0
Under the hood
1count = 1 triggers the Proxy set trap
2Notify the dependency tracker: count changed
3Find all components that depend on count
4Update the DOM automatically
Core idea: Vue uses Proxy to intercept reads and writes automatically, so the code feels natural.

3.3 مقارنة بين الطرق الثلاث

بُعد المقارنةVue (Proxy الوكيل)React (الاستدعاء الصريح)Svelte (المُترجم)
طريقة كتابة المطورإسناد مباشر count = 4يجب استخدام setCount(4)إسناد مباشر count = 4
توقيت الإحساس بالتغييراعتراض تلقائي وقت التشغيلالمطور يُخطر بشكل نشطإدراج كود الإخطار وقت الترجمة
تكلفة الأداء وقت التشغيلProxy له تكلفة اعتراض ضئيلةجدولة setState لها تكلفة ضئيلةتكاد لا تكون هناك تكلفة إضافية
صعوبة التنقيحمتوسطةتدفق البيانات واضح، أسهل نسبيًايحتاج لفهم الكود المُترجم
السيناريو المناسبالسعي لكفاءة التطوير وطريقة كتابة طبيعيةالسعي لتدفق بيانات يمكن التنبؤ بهالسعي لأقصى أداء تشغيلي

الطرق الثلاث ليس فيها صواب وخطأ مطلق. Vue يُكتب بشكل أكثر طبيعية، وReact لديه تدفق بيانات أكثر تحكمًا، وSvelte لديه أفضل أداء تشغيلي. اختيار أي منها يعتمد على متطلبات المشروع المحددة.


4. المكونات: تقسيم الواجهة إلى كتل صغيرة قابلة لإعادة الاستخدام

4.1 لماذا نقسم؟

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

المكون (Component) هو تقسيم الواجهة إلى كتل صغيرة مستقلة، كل كتلة تدير بياناتها الخاصة، وواجهتها الخاصة، ومنطقها الخاص.

على سبيل المثال، يمكن تقسيم صفحة تجارة إلكترونية إلى هذه المكونات:

  • مكون NavBar: مسؤول عن شريط التنقل العلوي
  • مكون SearchBox: مسؤول عن مربع البحث
  • مكون ProductCard: مسؤول عن بطاقة منتج واحدة
  • مكون ShoppingCart: مسؤول عن سلة التسوق

كل مكون مستقل. ProductCard لا يحتاج إلى معرفة ماذا كُتب في كود NavBar، هو فقط يدير نفسه.

4.2 ثلاث فوائد للمكونات

الفائدة الأولى: إعادة الاستخدام. بعد كتابة مكون ProductCard واحد، يمكن استخدامه 100 مرة على الصفحة — في كل مرة تُمرر بيانات منتج مختلفة، سيتم عرض بطاقة منتج مختلفة. لا حاجة لنسخ ولصق 100 نسخة من كود HTML.

الفائدة الثانية: التغليف. البيانات والمنطق داخل المكون مستقلان. تعديل كود مكون SearchBox لن يؤثر على مكون ProductCard. عند العمل الجماعي، يمكن لأشخاص مختلفين تطوير مكونات مختلفة في نفس الوقت دون التداخل مع بعضهم.

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

👇 جرّب النقر: انقر على اسم المكون على اليسار، لرؤية المنطقة المقابلة له على الصفحة. لاحظ أن مكون ProductCard نفسه أُعيد استخدامه عدة مرات، كل مرة يعرض بيانات مختلفة.

Component breakdownHow one page is split into independent components
Component tree
📱App (root component)
🧭NavBar
🔍SearchBox
🛒CartIcon
📦ProductCard×3
📄Footer
Page preview
🏠 E-commerce site🔍 Search box🛒 Cart (3)
📦
Product 1
¥199
📦
Product 2
¥298
📦
Product 3
¥397
📦 ProductCard
A card for one product. Write the code once, pass in different product data, and reuse it many times.
Independent dataIsolated stylesReused 3 times
Core idea:Componentization means splitting a large page into independent small pieces. Each component owns its own data, UI, and styles. The same component can be reused in multiple places with different input data.

4.3 كيف يبدو المكون في الكود؟

باستخدام Vue كمثال، المكون هو ملف .vue واحد، يحتوي على ثلاثة أجزاء:

html
<!-- ProductCard.vue -->
<template>
  <!-- هنا تُكتب بنية HTML — "المظهر" للمكون -->
  <div class="card">
    <h3>{{ name }}</h3>
    <p>السعر: ¥{{ price }}</p>
    <button @click="addToCart">أضف إلى السلة</button>
  </div>
</template>

<script setup>
// هنا يُكتب منطق JavaScript — "السلوك" للمكون
const props = defineProps(['name', 'price'])

function addToCart() {
  // معالجة منطق "إضافة إلى سلة التسوق"
}
</script>

<style scoped>
/* هنا تُكتب أنماط CSS — "التصميم" للمكون */
.card {
  border: 1px solid #ccc;
  padding: 16px;
}
</style>

عند استخدام هذا المكون، يكون الأمر كاستخدام علامة HTML مخصصة:

html
<!-- استخدام مكون ProductCard في مكان آخر -->
<ProductCard name="سماعات لاسلكية" price="299" />
<ProductCard name="لوحة مفاتيح ميكانيكية" price="599" />
<ProductCard name="شاشة عرض" price="1999" />

ثلاثة أسطر من الكود تعرض ثلاث بطاقات منتجات مختلفة.


5. تكلفة عمليات DOM: لماذا يبذل إطار العمل كل هذا الجهد؟

5.1 ما هي عملية DOM؟

سبق أن ذكرنا DOM — البنية الشجرية التي يُنشئها المتصفح بعد تحليل HTML. عملية DOM هي استخدام JavaScript لتعديل العُقد على هذه الشجرة. مثل تغيير نص، أو إضافة عنصر، أو حذف عنصر، أو تعديل نمط.

هذه العمليات بحد ذاتها ليست معقدة، لكن بعد تنفيذ عملية DOM، يحتاج المتصفح إلى القيام بالكثير من العمل الإضافي كي يُحدَّث العرض على الشاشة:

  1. إعادة حساب الأنماط: هل تحتاج أنماط CSS لهذه العقدة والعقد التابعة لها إلى تغيير؟
  2. إعادة التخطيط (Layout / Reflow): إعادة حساب مواقع وأحجام جميع العناصر على الصفحة. لأن تغيير عنصر واحد قد يؤثر على مواقع عناصر أخرى.
  3. إعادة الرسم (Paint): رسم المحتوى المُحسَب على الشاشة.

كل خطوة من الخطوات الثلاث لها تكلفة حسابية. إذا كان كودك يُشغّل عمليات DOM بشكل متكرر، فسيتكرر المتصفح في تنفيذ هذه الخطوات، وستصبح الصفحة بطيئة ومتقطعة.

👇 جرّب بنفسك: لاحظ مقارنة الوقت بين التعامل المباشر مع DOM والتعامل الدفعي. عندما يزداد عدد التعديلات، سيرتفع وقت "العمليات الفردية" بشكل حاد.

DOM operation cost comparisonOne-by-one operations vs batch operation
Update DOM one by one
Each data change immediately touches the real DOM, so the browser repeatedly lays out and paints.
Simulated time
1Change → layout → paint
2Change → layout → paint
3Change → layout → paint
4Change → layout → paint
... repeat 16 more times ...
Batch first, update once
All changes are computed in memory first, then committed to the real DOM once.
Simulated time
1Compute 20 changes in memory
2Commit once → layout → paint
Core idea:The real cost of DOM operations is not changing a value itself, but the layout and paint work the browser must do afterward. Reducing DOM writes reduces these expensive calculations.

5.2 كيف يحل إطار العمل هذه المشكلة؟

بما أن التعامل المباشر مع DOM مكلف، يسعى إطار العمل إلى تقليل عدد عمليات DOM. هناك استراتيجيتان محددتان:

الاستراتيجية الأولى: DOM الافتراضي + مقارنة الفروق (طريقة Vue وReact)

DOM الافتراضي (Virtual DOM) هو كائن JavaScript، بنيته تتوافق واحد لواحد مع شجرة DOM الحقيقية، لكنه موجود فقط في الذاكرة، ولا يُشغّل التخطيط والرسم في المتصفح.

عندما تتغير البيانات، يكون تدفق معالجة إطار العمل:

  1. استخدام كائن JavaScript لإنشاء "شجرة DOM افتراضية جديدة"، تصف كيف يجب أن تبدو الواجهة بعد تغير البيانات
  2. مقارنة الشجرة الجديدة بالشجرة القديمة (هذه العملية تُسمى Diff، أي مقارنة الفروق)، لمعرفة أي العُقد قد تغيرت
  3. تطبيق الجزء الذي تغير فعلًا على DOM الحقيقي فقط (هذه العملية تُسمى Patch، أي الترقيع)

بهذه الطريقة، مهما تغيرت البيانات، فإن العمليات على DOM الحقيقي تكون دائمًا في حدها الأدنى.

👇 جرّب النقر: انقر "تعديل البيانات"، ولاحظ كيف يقارن DOM الافتراضي بين الشجرتين الجديدة والقديمة لمعرفة العُقد المتغيرة. انتبه لـ "DOM الحقيقي" في أقصى اليمين — فقط الجزء الذي تغير فعلًا سيومض.

Virtual DOM diff processThe core mechanism for minimizing DOM updates
Old VTree
div.app
h1: Todo list
ul.list
li: Learn Vue
li: Do homework
li: Play games
Diff Result
div.app
h1: Todo list
ul.list
li: Learn Vue
li: Do homework
li: Play games
Real DOM
div.app
h1: Todo list
  • Learn Vue
  • Do homework
  • Play games
7
Total virtual DOM nodes
0
Real DOM updates needed
Saved DOM operations
Core idea: The virtual DOM compares old and new trees in memory, finds the minimal difference, and updates only the necessary real DOM nodes.

الاستراتيجية الثانية: التحديد الدقيق وقت الترجمة (طريقة Svelte)

Svelte لا يستخدم DOM الافتراضي. يقوم مُترجمه بالتحليل مسبقًا عند كتابتك للكود: "عندما يتغير count، يجب تحديث عنصر <span> في السطر 3". وقت التشغيل يحدد ذلك العنصر مباشرة للتحديث، دون الحاجة أصلًا لمقارنة الشجرتين الجديدة والقديمة.

هذه الطريقة تتخطى خطوة Diff، وأداؤها أفضل نظريًا. لكنها تعتمد على قدرة المُترجم على التحليل — يحتاج المُترجم إلى أن يكون ذكيًا بما يكفي لتحديد جميع الأماكن التي تحتاج تحديثًا بشكل صحيح.


6. وقت التشغيل مقابل وقت الترجمة: المقايضة الجوهرية في تصميم أُطُر العمل

6.2 مرحلتان

من كتابتك للكود الأمامي حتى تشغله في المتصفح في النهاية، يمر بمرحلتين:

  • وقت الترجمة (Compile-time / Build-time): يُعالَج كودك المصدري بواسطة أدوات البناء (مثل Vite وWebpack)، ويُحوَّل إلى كود يمكن للمتصفح تنفيذه مباشرة. هذه العملية تحدث على جهازك، قبل أن يفتح المستخدم صفحة الويب.
  • وقت التشغيل (Runtime): يُنفَّذ الكود المُحوَّل في متصفح المستخدم. المنطق الأساسي لإطار العمل (مثل Diff للـ DOM الافتراضي، وتتبع التفاعلية) يعمل في هذه المرحلة.

6.2 توزيع عمل إطار العمل في المرحلتين

تختلف أُطُر العمل المختلفة في حجم العمل المُوزَّع في المرحلتين، مما يحدد خصائص الأداء وحجم الحزمة:

  • React: معظم العمل يُنجَز في وقت التشغيل. إنشاء DOM الافتراضي وDiff وPatch تحدث كلها في المتصفح. الميزة هي المرونة العالية؛ والثمن هو ضرورة إرسال كود وقت التشغيل الكامل لإطار العمل (حوالي 40 كيلوبايت) إلى المتصفح.
  • Vue: طريقة مختلطة. يُحسَّن القالب وقت الترجمة (يُعلِّم المُترجم أي العُقد ثابتة ولن تتغير)، لكن تحديث الواجهة النهائي لا يزال يتم عبر DOM الافتراضي وقت التشغيل. حجم كود وقت التشغيل حوالي 30 كيلوبايت.
  • Svelte: معظم العمل يُنجَز وقت الترجمة. يُحلل المُترجم كودك، ويُنشئ مباشرة تعليمات تحديث DOM دقيقة. وقت التشغيل تكاد لا تكون هناك أكواد إطار عمل — الحزمة الناتجة تحتوي فقط على كود أعمالك الخاص. حجم الحزمة هو الأصغر.

👇 جرّب النقر: انقر على تبويبات أُطُر العمل المختلفة، لرؤية مواقعها على الطيف "وقت التشغيل ↔ وقت الترجمة"، والمقايضات في حجم الحزمة وأداء التشغيل وتجربة التطوير.

Framework spectrumRuntime ↔ compile time
More runtimeMore compile time
ReactVue 3Vue VaporSvelteSolid.js
💚Vue 3
Hybrid: compiled templates + runtime virtual DOM
Runtime work
60%
Compile-time work
40%
Bundle sizeMediumDeveloper experience★★★★★
Trend: The trend is clear: frameworks keep moving work from runtime to compile time to improve both developer experience and runtime performance.

6.3 اتجاه الصناعة

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

  • Vue يُطوّر حاليًا وضع Vapor، الذي يمكنه تخطي DOM الافتراضي، وإنشاء كود عمليات DOM مباشرة وقت الترجمة
  • React أطلق React Compiler، الذي يُحسِّن تلقائيًا سلوك إعادة العرض للمكونات وقت الترجمة
  • Svelte 5 قدّم نظام Runes، لتعزيز قدرة التحليل وقت الترجمة بشكل أكبر

7. الخلاصة

دعنا نراجع النقاط الجوهرية في هذه المقالة:

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

الفكرة الجوهرية المشتركة: UI = f(State) — الواجهة هي دالة للبيانات، يحتاج المطور فقط إلى الاهتمام بتغير البيانات، وإطار العمل مسؤول عن عكس تغير البيانات على الواجهة.

الفروقات التقنية الرئيسية بينها:

النقطة التقنيةالمعنى
نظام التفاعليةكيف يكتشف إطار العمل تغير البيانات. Vue يستخدم اعتراض Proxy، وReact يستخدم setState الصريح، وSvelte يستخدم تحليل المُترجم.
DOM الافتراضيVue وReact يستخدمان كائن JavaScript لمحاكاة شجرة DOM، ومن خلال مقارنة الشجرتين الجديدة والقديمة (Diff) لإيجاد الحد الأدنى من التحديث، وتقليل عمليات DOM الحقيقية.
المكوناتتقسيم الواجهة إلى كتل مستقلة وقابلة لإعادة الاستخدام، كل مكون يدير بياناته وواجهته الخاصة.
تحسين وقت الترجمةالتحليل والتحسين المسبق في مرحلة البناء، لتقليل حجم الحسابات وقت التشغيل. Svelte هو الأبعد في هذا الجانب.

بجملة واحدة: العمل الجوهري لأُطُر العمل الأمامية هو — تولي عملية المزامنة "من البيانات إلى الواجهة"، بحيث يحتاج المطور فقط إلى التفكير في منطق البيانات، دون الحاجة للتعامل مع الواجهة يدويًا.


جدول المصطلحات

المصطلح الإنجليزيالمقابل العربيالشرح
Frameworkإطار العملمجموعة من الكود والقواعد المكتوبة مسبقًا، توفر للمطور البنية الأساسية للتطبيق والوظائف الشائعة.
DOMنموذج كائن المستندبنية بيانات شجرية يُنشئها المتصفح بعد تحليل HTML، يتعامل JavaScript معها لتعديل الصفحة.
Virtual DOMDOM الافتراضياستخدام كائن JavaScript لمحاكاة شجرة DOM، ومن خلال خوارزمية Diff لإيجاد مسار التحديث الأصغر، وتقليل عدد عمليات DOM الحقيقية.
Stateالحالةالبيانات في التطبيق، مثل معلومات المستخدم، محتويات سلة التسوق، الحالة الحالية للصفحة وغيرها.
Reactivityالتفاعليةعندما تتغير البيانات، يستطيع النظام الإحساس بالتغيير تلقائيًا وتنفيذ عمليات تحديث الواجهة المقابلة.
Proxyالوكيلآلية مدمجة في JavaScript، يمكنها اعتراض عمليات القراءة والكتابة على كائن. يستخدمها Vue 3 لتنفيذ التفاعلية.
Componentالمكونقطعة كود واجهة مستقلة وقابلة لإعادة الاستخدام، تحتوي على بنية HTML الخاصة بها ومنطق JavaScript وأنماط CSS.
Declarativeتقريريأسلوب برمجة: أنت تصف "النتيجة النهائية التي تريدها"، ويُقرر إطار العمل كيفية التنفيذ.
Imperativeأمريأسلوب برمجة: أنت تُخبر البرنامج خطوة بخطوة "ماذا يفعل تحديدًا".
Diffمقارنة الفروقمقارنة شجرتَي DOM الافتراضي الجديدة والقديمة، لمعرفة أي العُقد قد تغيرت.
Patchالترقيعتطبيق التغييرات التي وجدتها Diff على DOM الحقيقي.
Compile-timeوقت الترجمةالفترة التي يُعالَج فيها الكود في مرحلة البناء، وتحدث قبل أن يفتح المستخدم صفحة الويب.
Runtimeوقت التشغيلالفترة التي يُنفَّذ فيها الكود في متصفح المستخدم.
Compilerالمُترجمبرنامج يحوّل الكود المصدري إلى شكل آخر من الكود. مُترجم Svelte يحوّل ملفات .svelte إلى JavaScript عالي الكفاءة.