أطر العمل
أساسيات React: من الصفر إلى الاحتراف
دليل عملي شامل للنموذج الذهني الذي يحتاجه كل مطوّر واجهات أمامية في React — لماذا وُجدت React، والتفكير التصريحي (الواجهة = دالّة الحالة)، والمكوّنات وJSX، والخصائص المُنمَّطة بـ TypeScript وتدفّق البيانات باتجاه واحد، وعرض القوائم والمفاتيح، والعرض الشرطي، ومعالجة الأحداث، والحالة عبر useState، والتحديثات الثابتة، ونموذج إعادة العرض، والنقاء والتركيب، والأخطاء الشائعة، مع تمارين عملية وحلولها.
يتعلّم معظم الناس React كركامٍ من الصياغة — useState هنا، و.map() مع key هناك — دون إدراك الفكرة الواحدة التي تجعل كل ذلك بديهيًّا: واجهتك دالّةٌ لحالتك. أدرِكها، وتتوقّف JSX وإعادة العرض والمفاتيح والثباتية وقواعد الخطّافات عن كونها قواعد للحفظ وتصير أشياء تشتقّها. هذا هو ذلك النموذج الذهني، مبنيًّا من الأساس. يفترض أنك مرتاح مع أساسيات JavaScript — الدوال كقيم، والتفكيك، والنشر، وتوابع المصفوفات مثل map — لأن React هي مجرّد JavaScript، لا لغة جديدة.
سنكتب TypeScript طوال الوقت، لأن هكذا تُكتَب React الاحترافية وقد غطّيت من قبل مقدّمة TypeScript: الخصائص المُنمَّطة تجعل المكوّنات موثِّقةً لذاتها وتلتقط فئةً كاملة من العلل قبل تشغيلها. لا تدع التنميطات تشتّتك — فكل مثالٍ هو نفس React بعد نزع الأنواع، فإن كنت على .jsx عاديّة فتجاهل أجزاء : type ببساطة.
الفكرة الوحيدة لترسيخها: في React لا تلمس DOM مباشرةً أبدًا. أنت تصِف كيف ينبغي أن تبدو الشاشة للحالة الراهنة، وReact تحسب أدنى تغييرات DOM اللازمة للوصول إليها. اكتب
الواجهة = دالّة(الحالة)، غيّر الحالة، فتتبعها الواجهة. تقريبًا كل مفاهيم React تنبع من تلك المعادلة الواحدة.
لماذا وُجدت React
قبل React، كان بناء واجهةٍ تفاعلية يعني التلاعب اليدويّ بـ DOM: جِد عنصرًا، غيّر نصّه، أضِف صنفًا، احذف عقدة، وأبقِ كل ذلك متزامنًا مع بياناتك يدويًّا. يعمل هذا حتى يتوقّف — فمع نموّ التطبيق، ينفجر عدد قواعد "حين يتغيّر X، تذكّر تحديث Y وZ"، وتكون العلل دائمًا في زاويةٍ نسيت تحديثها.
تقلب React النموذج. فبدل كتابة خطواتٍ لتغيير الصفحة (أمريّ)، تكتب ما ينبغي أن تكون عليه الصفحة لأيّ بياناتٍ معطاة (تصريحيّ):
// أمريّ (DOM الخام): تدير كل انتقالٍ بيدك
if (count > 0) {
badge.textContent = String(count);
badge.classList.remove("hidden");
} else {
badge.classList.add("hidden");
}
// تصريحيّ (React): تصِف النتيجة، وReact تزامن DOM
return count > 0 ? <span className="badge">{count}</span> : null;تكفّ عن التفكير في كيفية تحديث DOM وتبدأ التفكير في ما ينبغي أن يعرضه. React تتكفّل بالمقارنة والترقيع. تلك هي القيمة الجوهرية بأكملها.
الإعداد (30 ثانية)
لا تحتاج سلسلة أدواتٍ معقّدة للبدء. Vite يُنشئ مشروع React + TypeScript حديثًا فورًا:
npm create vite@latest my-app -- --template react-ts
cd my-app && npm install && npm run devيمنحك ذلك خادم تطوير بإعادة تحميلٍ فورية وTypeScript موصولةً سلفًا (ملفّات .tsx). كل ما في هذه التدوينة يعمل في تلك البيئة — بلا حاجة إلى إطار عمل. (تُضيف الأُطر الوصفية مثل Next.js التوجيه والعرض على الخادم فوق ذلك، لكن الأساسيات متطابقة.)
التفكير التصريحي: الواجهة = دالّة(الحالة)
هذا هو التحوّل النموذجيّ كله، فيستحقّ أن يُقال بوضوح. تطبيق React دالّةٌ تأخذ الحالة (بياناتك) وتُرجِع وصفًا للواجهة. وحين تتغيّر الحالة، تستدعي React دالّتك ثانيةً وتحدّث الشاشة لتطابق.
الحالة ──▶ مكوّناتك ──▶ وصف الواجهة ──▶ React تحدّث DOM
▲ │
└────────── الأحداث تغيّر الحالة ◀────────────────┘
لا تكتب أبدًا "غيّر هذا النصّ" أو "أخفِ ذلك العنصر". بل تكتب "لـهذه الحالة، تبدو الواجهة هكذا"، ثم تغيّر الحالة. وReact تتولّى الباقي. أمسِك بهذه الحلقة — فكل قسمٍ أدناه جزءٌ منها.
المكوّنات: دوالٌّ تُرجِع واجهة
المكوّن دالّة JavaScript تُرجِع قطعةً من الواجهة. هذا كلّ شيء. واصطلاحًا يبدأ اسمه بـحرفٍ كبير (كي تميّز React مكوّناتك عن وسوم HTML).
function Welcome() {
return <h1>Hello, world</h1>;
}
// المكوّنات تتركّب — تستخدمها كوسوم HTML مخصّصة
function App() {
return (
<main>
<Welcome />
<Welcome />
</main>
);
}المكوّنات هي وحدة إعادة الاستخدام والتركيب في React. التطبيق الحقيقيّ شجرةٌ منها: App يعرض Header وSidebar وFeed؛ وFeed يعرض مكوّنات Post كثيرة؛ وهكذا. تبني واجهاتٍ كبيرة بتعشيق دوالٍّ صغيرةٍ مركّزة.
ملاحظة: سترى أيضًا مكوّنات الأصناف (class components) في الكود الأقدم (
class Welcome extends React.Component). لا تزال تعمل، لكن مكوّنات الدوال مع الخطّافات هي المعيار الحديث لكل شيء — وهذه التدوينة تستخدم الدوال حصرًا.
JSX: HTML داخل JavaScript
ذلك <h1>Hello</h1> داخل الدالّة ليس نصًّا وليس HTML — إنه JSX، امتداد صياغيّ يتيح كتابة الوسوم داخل JavaScript. تُترجِمه أداة بناءٍ إلى استدعاءات دوالٍّ عادية قبل التشغيل. تكتبه غالبًا كـ HTML، مع فوارق مهمّة قليلة:
function Card({ title, done }: { title: string; done: boolean }) {
return (
<div className="card"> {/* class ← className (class كلمة محجوزة في JS) */}
<label htmlFor="agree">Title</label> {/* for ← htmlFor */}
<h2>{title}</h2> {/* {} تُدخِلك إلى JavaScript */}
<p>{done ? "✅ Done" : "⏳ Pending"}</p> {/* أيّ تعبير JS يعمل */}
<img src="/logo.png" alt="" /> {/* الوسوم يجب أن تُغلَق ذاتيًّا */}
</div>
);
}القواعد التي تُعثِر المبتدئين:
{ }منفذٌ إلى JavaScript. أيّ شيءٍ بين القوسين تعبير JS — متغيّر، أو استدعاء دالّة، أو شرطيّ ثلاثيّ، أو.map(). أما التعليمات مثلifوforفلا تذهب هنا.classNameلاclass، و**htmlForلاfor** — لأنclassوforكلمتان محجوزتان في JavaScript. ومعظم السمات الأخرى بصيغة camelCase (onClick،tabIndex).- كل وسمٍ يجب أن يُغلَق —
<img />،<br />،<input />. - المكوّن يُرجِع عنصرًا جذريًّا واحدًا. ولإرجاع أشقّاء بلا غلافٍ
<div>، استخدم Fragment:<>...</>.
return (
<>
<Header />
<Main />
</> // Fragment: يجمع الأبناء بلا عقدة DOM إضافية
);الخصائص: تمرير البيانات نزولًا
تصير المكوّنات مفيدةً حين تكون قابلةً للضبط. الخصائص (props) هي الوسائط التي تمرّرها إلى مكوّن — تمامًا كمعاملات الدالّة. تمرّرها كسمات؛ ويتلقّاها المكوّن ككائنٍ واحد، تُفكّكه غالبًا:
// الأب يمرّر الخصائص (كسمات HTML)
<Greeting name="Ada" excited={true} />
<Avatar user={currentUser} size={48} />
// الابن يتلقّاها — فكّكها للوصول النظيف
function Greeting({ name, excited = false }: { name: string; excited?: boolean }) {
return <p>Hello, {name}{excited ? "!" : "."}</p>;
}تنميط الخصائص
في TypeScript تصِف خصائص المكوّن بـ type (أو interface) — وهذا أنفع موضعٍ تُثبِت فيه الأنواع جدواها، لأنها توثّق بالضبط ما يحتاجه المكوّن وتُخطئ لحظة يمرّر المستدعي الشيء الخطأ:
type GreetingProps = {
name: string;
excited?: boolean; // العلامة ? تجعلها اختيارية
};
function Greeting({ name, excited = false }: GreetingProps) {
return <p>Hello, {name}{excited ? "!" : "."}</p>;
}
<Greeting name="Ada" /> // ✅
<Greeting excited={true} /> // ❌ خطأ ترجمة: name مطلوبة
<Greeting name={42} /> // ❌ خطأ ترجمة: name يجب أن تكون نصًّاقاعدتان تُعرّفان كيف تعمل الخصائص، وهما بالغتا الأهمّية:
- البيانات تتدفّق باتجاهٍ واحد: نزولًا. يمرّر الأب الخصائص للابن، ولا يستطيع الابن تغييرها. هذا "التدفّق باتجاهٍ واحد" هو ما يجعل تطبيقات React متوقّعة — تعرف دائمًا من أين جاءت القيمة (الأب فوقها).
- الخصائص للقراءة فقط. يجب ألّا يعدّل المكوّن خصائصه أبدًا. عاملها كلقطةٍ للقراءة فقط لهذا العرض. وإن احتاج الابن تغيير شيء، يمرّر له الأب دالّةً يستدعيها (المزيد في قسم الأحداث).
children: المكوّنات كأوعية
كل ما تعشّقه بين وسمَي المكوّن يصل كخاصّةٍ مميّزة اسمها children، مُنمَّطةً بـ React.ReactNode (أيّ شيءٍ قابل للعرض). هكذا تبني أغلفةً قابلةً لإعادة الاستخدام:
type PanelProps = {
title: string;
children: React.ReactNode;
};
function Panel({ title, children }: PanelProps) {
return (
<section className="panel">
<h2>{title}</h2>
<div className="panel__body">{children}</div>
</section>
);
}
// الاستخدام — أيّ شيءٍ بالداخل يصير children
<Panel title="Settings">
<p>Put whatever you want here.</p>
<button>Save</button>
</Panel>children مفتاح التركيب — فـPanel أو Card أو Modal لا يحتاج معرفة ما يغلّفه.
عرض القوائم بـ .map() والمفاتيح
لا صياغة حلقةٍ خاصّة في JSX — تعرض قائمةً بتحويل مصفوفة بياناتٍ إلى مصفوفة عناصر، بنفس map التي تعرفها:
type Todo = { id: number; text: string };
function TodoList({ todos }: { todos: Todo[] }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li> // كل عنصرٍ يحتاج مفتاحًا ثابتًا
))}
</ul>
);
}المفتاح key مطلوب، وأثره أكبر ممّا يبدو. تستخدم React المفاتيح لمطابقة العناصر بين عمليات العرض كي تعرف ما أُضيف أو أُزيل أو أُعيد ترتيبه — فتحدّث تلك فقط، بدل إعادة بناء القائمة كلها.
- استخدم معرّفًا ثابتًا وفريدًا من بياناتك (
todo.id،user.email). - لا تستخدم فهرس المصفوفة مفتاحًا حين يمكن للقائمة أن يُعاد ترتيبها أو تُرشَّح أو يُدرَج/يُحذَف منها عناصر. فإن أزاحت العناصر مواضعها، تجعل مفاتيح الفهارس React تربط البيانات الخطأ بالعنصر الخطأ — مسبّبةً عللًا خفيّة مثل قفز نصّ حقلٍ إلى الصفّ الخطأ. مفاتيح الفهارس آمنةٌ فقط لقائمةٍ ساكنة لا يتغيّر ترتيبها أبدًا.
العرض الشرطي
لأن قيم JSX مجرّد تعابير، تُظهِر الواجهة أو تُخفيها بـ JavaScript عاديّة:
type StatusProps = { user: { name: string } | null; unread: number };
function Status({ user, unread }: StatusProps) {
return (
<div>
{/* شرطيّ ثلاثيّ: أحد فرعين */}
{user ? <p>Welcome, {user.name}</p> : <a href="/login">Sign in</a>}
{/* && : يعرض الجانب الأيمن فقط إن كان الأيسر صادقًا */}
{unread > 0 && <span className="badge">{unread}</span>}
{/* أرجِع null كي لا يُعرَض شيء */}
{!user && null}
</div>
);
}فخٌّ كلاسيكيّ مع &&: إن كان الجانب الأيسر العدد 0، تعرض React 0 على الشاشة بدل لا شيء، لأن 0 كاذبٌ لكنه قيمةٌ قابلة للعرض. احرُس بقيمةٍ منطقية حقيقية:
{items.length > 0 && <List items={items} />} // ✅ قيمة منطقية على اليسار
{items.length && <List items={items} />} // ❌ يعرض "0" حين تكون فارغةمعالجة الأحداث
تستجيب لتفاعل المستخدم عبر معالِجات أحداثٍ تُمرَّر كخصائص مثل onClick وonChange وonSubmit. تمرّر دالّة — لا تستدعيها. وTypeScript تُنمِّط كائن الحدث لك (React.FormEvent، React.ChangeEvent، React.MouseEvent):
function SearchBar() {
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault(); // أوقِف الإرسال الافتراضي للمتصفّح
console.log("searching…");
}
return (
<form onSubmit={handleSubmit}>
<input onChange={(e: React.ChangeEvent<HTMLInputElement>) => console.log(e.target.value)} />
<button onClick={() => console.log("clicked")}>Go</button>
</form>
);
}التمييز الذي يلدغ المبتدئين:
<button onClick={handleClick}> {/* ✅ مرّر الدالّة؛ React تستدعيها عند النقر */}
<button onClick={handleClick()}> {/* ❌ تستدعيها الآن، أثناء العرض، في كل عرض */}
<button onClick={() => handleClick(id)}> {/* ✅ لُفّها بسهمٍ لتمرير الوسائط */}إن احتجت تمرير وسيط، لُفّ الاستدعاء في دالّةٍ سهمية داخلية كي تحصل React على دالّةٍ تستدعيها لاحقًا، لا على النتيجة الآن.
الحالة: جعل المكوّنات تتذكّر
الخصائص تأتي من الأب وهي للقراءة فقط. لكن المكوّن يحتاج غالبًا ذاكرته الخاصّة التي تتغيّر عبر الزمن — نصّ حقلٍ، أو هل القائمة مفتوحة، أو عدّاد. تلك الحالة (state)، وتُنشئها بخطّاف useState:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0); // TS تستنتج `count: number` من القيمة الابتدائية
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}يُرجِع useState(0) زوجًا: القيمة الراهنة ودالّة مُحدِّث (setter). تستنتج TypeScript النوع من القيمة الابتدائية، فـuseState(0) هي number وuseState("") هي string — بلا تنميط. وحين لا تستطيع القيمة الابتدائية التعبير عن النوع الكامل (مثلًا تبدأ null لكنها ستحمل مستخدمًا)، مرّر النوع صراحةً بمعامل نوعيّ (generic):
type User = { name: string };
const [user, setUser] = useState<User | null>(null); // الآن يمكن أن تكون User أو null
const [tags, setTags] = useState<string[]>([]); // المصفوفة الفارغة تحتاج تلميح نوعوهنا الجزء الجوهريّ الذي يعود بنا إلى الواجهة = دالّة(الحالة):
استدعاء المُحدِّث لا يغيّر متغيّرًا فحسب — بل يُخبِر React أن تعيد تشغيل هذا المكوّن بالقيمة الجديدة وتحدّث الشاشة. لا تكتب count++ أبدًا ولا تلمس DOM. تستدعي setCount، فتعيد React عرض Counter، ويعكس JSX الجديد العدّ الجديد. الحالة هي مُدخَل دالّة واجهتك؛ وتغييرها يعيد تشغيل الدالّة.
(هذا مذاقٌ أوّل — الخطّافات مثل useState وuseEffect وuseRef والقواعد التي تحكمها تُعالَج كاملةً في تدوينة الحالة والخطّافات المخصّصة. هنا نحتاج فقط ما يكفي من الحالة لإحياء المكوّنات.)
الحالة لقطة — حدّثها بثباتية
قاعدةٌ دقيقة لكنها جوهرية: لا تغيّر الحالة مباشرةً أبدًا. تقرّر React إعادة العرض بمقارنة المراجع، فعليك إنشاء كائنٍ أو مصفوفةٍ جديدة بدل تغيير القائمة — نفس عادة الثباتية من JavaScript الوظيفية.
const [user, setUser] = useState({ name: "Ada", age: 36 });
const [items, setItems] = useState([1, 2, 3]);
// ❌ تغيير — قد لا تلاحظه React؛ لن تتحدّث الواجهة
user.age = 37;
items.push(4);
// ✅ أنشئ قيمًا جديدة بالنشر
setUser({ ...user, age: 37 }); // كائن جديد، حقلٌ واحد تغيّر
setItems([...items, 4]); // مصفوفة جديدة بالإضافة
setItems(items.filter((n) => n !== 2)); // مصفوفة جديدة بلا عنصرحين تعتمد الحالة التالية على السابقة، مرّر دالّةً إلى المُحدِّث بدل قيمة. تعطيك React أحدث حالة، ما يتجنّب العلل عند تجميع التحديثات:
setCount((c) => c + 1); // ✅ آمن حتى لو استُدعي عدّة مرّاتٍ متتالية
// setCount(count + 1) قد يصير قديمًا إن التُقط count من عرضٍ سابقنموذج إعادة العرض: ما الذي يُطلِق تحديثًا فعلًا
اربط كل ذلك بنموذج متى تعيد React تشغيل مكوّنك. يُعاد عرض المكوّن حين:
- تتغيّر حالته (استُدعي مُحدِّث)، أو
- تتغيّر خصائصه لأن أباه أُعيد عرضه.
حين يُعاد عرض مكوّن، تعيد React تشغيل دالّته، فتُنتِج وصف JSX جديدًا، تقارنه بالسابق ("DOM الافتراضيّ")، وتطبّق الفوارق فقط على DOM الحقيقيّ. لهذا تبدو React سريعةً رغم "إعادة العرض" — فهي تعيد تشغيل دوالّك، لا إعادة بناء الصفحة.
function Parent() {
const [n, setN] = useState(0);
return (
<div>
<button onClick={() => setN(n + 1)}>+</button>
<Child value={n} /> {/* يُعاد عرضه كلما تغيّر n، لأن خاصّته تغيّرت */}
</div>
);
}
function Child({ value }: { value: number }) {
return <p>{value}</p>;
}الخلاصة العملية: العرض رخيص ويحدث كثيرًا. لا تخفه — لكن أبقِ مكوّناتك سريعةً ونقيّة (التالي)، لأنها تعمل في كل تحديث.
النقاء: يجب أن تكون المكوّنات متوقّعة
تفترض React أن مكوّناتك نقيّة أثناء العرض: بنفس الخصائص والحالة، يجب أن يُرجِع المكوّن نفس JSX ويجب ألّا يُحدِث آثارًا جانبية أثناء العرض — لا جلب بيانات، ولا كتابة إلى متغيّراتٍ خارجه، ولا تغيير يدويّ لـ DOM، ولا setState أثناء العرض.
// ❌ غير نقيّ — يغيّر حالةً خارجية أثناء العرض
let total = 0;
function Item({ price }: { price: number }) {
total += price; // أثرٌ جانبيّ أثناء العرض — غير متوقّع
return <li>{price}</li>;
}
// ✅ نقيّ — يشتقّ كل شيءٍ من مُدخَلاته، بلا تغييراتٍ خارجية
function Item({ price }: { price: number }) {
return <li>${price.toFixed(2)}</li>;
}الآثار الجانبية التي يجب أن تحدث — جلب بيانات، اشتراكات، مؤقّتات، تركيز حقلٍ يدويًّا — تنتمي إلى أثر (useEffect)، يعمل بعد العرض لا أثناءه. (هذا موضوعٌ محوريّ لتدوينة الحالة والخطّافات.) القاعدة التي تمسكها الآن: العرض يحسب الواجهة؛ ولا يغيّر العالم أبدًا. النقاء هو ما يتيح لـ React إعادة تشغيل مكوّناتك وإعادة ترتيبها وتخطّيها بأمان.
التركيب فوق الضبط
جواب React عن "كيف أشارك الواجهة وأعيد استخدامها" هو التركيب — دمج مكوّناتٍ صغيرة — لا الوراثة ولا المكوّنات العملاقة القابلة للضبط. تمرّر المكوّنات إلى بعضها عبر children والخصائص:
type LayoutProps = {
sidebar: React.ReactNode;
children: React.ReactNode;
};
function Layout({ sidebar, children }: LayoutProps) {
return (
<div className="layout">
<aside>{sidebar}</aside> {/* مكوّن مُمرَّر كخاصّة */}
<main>{children}</main> {/* محتوًى معشّق */}
</div>
);
}
<Layout sidebar={<Nav />}>
<Article />
</Layout>إن وجدت نفسك تضيف خصائص منطقية لا تنتهي (isModal، hasHeader، variant...) إلى مكوّنٍ واحد، فتلك عادةً إشارةٌ إلى تقسيمه إلى قطعٍ أصغر قابلة للتركيب. (تتعمّق تدوينة المكوّنات والخصائص في هذه الأنماط.)
الأخطاء الشائعة
- تغيير الحالة بدل استبدالها —
arr.push(x)أوobj.key = vثمsetState. تقارن React بالمرجع فلا تعيد العرض. انشُر إلى قيمةٍ جديدة. - استخدام فهرس المصفوفة مفتاحًا في قائمةٍ قد يُعاد ترتيبها أو تتغيّر — يؤدّي إلى علل العنصر الخطأ. استخدم معرّفًا ثابتًا.
- استدعاء معالِجٍ بدل تمريره —
onClick={handleClick()}يعمل أثناء العرض. مرّرonClick={handleClick}أو لُفّ:onClick={() => handleClick(id)}. - فخّ الصفر
count && <X/>—0على يسار&&يعرض0. استخدم قيمةً منطقية صريحة:count > 0 && .... - محاولة تعديل الخصائص — الخصائص للقراءة فقط. لتغيير البيانات، ارفعها إلى حالة الأب ومرّر مُحدِّثًا نزولًا.
- إجراء آثارٍ جانبية أثناء العرض — الجلب أو الاشتراك في جسم المكوّن. ضعها في
useEffect. - نسيان أن تحديثات الحالة غير متزامنة — قراءة
countمباشرةً بعدsetCountتعطي القيمة القديمة؛ الجديدة تظهر في العرض التالي. استخدم المُحدِّث الدالّيّ حين تعتمد القيمة التالية على السابقة. - أسماء مكوّناتٍ بحرفٍ صغير —
<welcome />يُعامَل كوسم HTML. المكوّنات يجب أن تبدأ بحرفٍ كبير.
تمارين
جرّب كلًّا قبل فتح الحلّ.
تمرين 1 — خصائص مُنمَّطة بقيمة افتراضية
اكتب مكوّن Badge يعرض خاصّة label، بقيمةٍ افتراضية "New" حين لا تُمرَّر. نمِّط الخصائص بحيث تكون label نصًّا اختياريًّا. <Badge /> يعرض "New"؛ و<Badge label="Sale" /> يعرض "Sale".
اعرض الحل
function Badge({ label = "New" }: { label?: string }) {
return <span className="badge">{label}</span>;
}فكّك الخاصّة بقيمةٍ افتراضية مباشرةً في قائمة المعاملات، وعلّمها اختياريةً بـ ? في النوع — نفس صياغة المعامل الافتراضيّ في JavaScript، لكن مع فحص الأنواع الآن.
تمرين 2 — اعرض قائمة
بمعطى const users = [{ id: 1, name: "Ada" }, { id: 2, name: "Grace" }]، اعرض <li> لكل اسم.
اعرض الحل
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>يحوّل map مصفوفة البيانات إلى مصفوفة عناصر. وkey={user.id} معرّفٌ فريدٌ ثابت — لا الفهرس أبدًا لبياناتٍ قد يتغيّر ترتيبها.
تمرين 3 — زرّ بحالة
ابنِ زرًّا يبدّل بين "ON" و"OFF" في كل نقرة.
اعرض الحل
function Toggle() {
const [on, setOn] = useState(false); // مُستنتَجة boolean
return (
<button onClick={() => setOn((prev) => !prev)}>
{on ? "ON" : "OFF"}
</button>
);
}المُحدِّث الدالّيّ (prev) => !prev يقلب القيمة المنطقية بناءً على أحدث قيمة. النقر يستدعي المُحدِّث، فيُعيد عرض المكوّن بالحالة الجديدة.
تمرين 4 — أضِف عنصرًا بثباتية
بمعطى const [tags, setTags] = useState<string[]>(["react"])، اكتب معالِج النقر الذي يُلحِق "js" دون تغيير المصفوفة.
اعرض الحل
const addTag = () => setTags((prev) => [...prev, "js"]);انشُر المصفوفة السابقة إلى جديدةٍ مع العنصر الإضافيّ. أما التغيير بـ tags.push("js") فيغيّر المصفوفة في المكان ولن تعيد React العرض بموثوقية.
تمرين 5 — توقّع العرض
function App() {
const [count, setCount] = useState(0);
console.log("render", count);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}كم مرّةً يُسجَّل "render" بعد نقرتين، وبأيّ قيم؟
اعرض الحل
ثلاثة تسجيلات: render 0 (الابتدائيّ)، ثم render 1 (النقرة الأولى)، ثم render 2 (النقرة الثانية). كل setCount يجدول إعادة عرض، فتعيد React تشغيل دالّة المكوّن، ويعمل console.log ثانيةً بالحالة الجديدة. هذا الواجهة = دالّة(الحالة) عمليًّا — حالةٌ جديدة، فتعمل الدالّة ثانية.
النموذج الذهني الذي تحتفظ به
تُبنى React على معادلةٍ واحدة: الواجهة = دالّة(الحالة). تكتب مكوّنات — دوالٌّ عادية تُرجِع وصف JSX للواجهة — ولا تلمس DOM بنفسك أبدًا؛ بل تغيّر الحالة وتدع React تزامن الشاشة. تتدفّق البيانات باتجاهٍ واحد، نزولًا، عبر خصائص للقراءة فقط (تتيح لك TypeScript وصفها بدقّة، فيفشل الاستخدام الخطأ في الترجمة بدل وقت التشغيل)؛ وحين يحتاج الابن تغيير شيء، يمرّر الأب دالّةً نزولًا. يُعاد عرض المكوّن حين تتغيّر حالته أو خصائصه، فتعيد React تشغيل الدالّة وتقارن النتيجة وترقّع ما اختلف فقط — فأبقِ المكوّنات نقيّة (لا آثار جانبية أثناء العرض؛ تلك تذهب إلى الآثار) وحدّث الحالة بثباتية (انشُر إلى كائناتٍ ومصفوفاتٍ جديدة، لا تغيّر أبدًا). اعرض القوائم بـ map ومفاتيح ثابتة، وأظهِر وأخفِ بتعابير عادية، وابنِ واجهاتٍ كبيرة بـتركيب مكوّناتٍ صغيرة عبر children والخصائص. رسّخ حلقة الواجهة = دالّة(الحالة) وسيصير كل ما عداها في React — الخطّافات، والسياق، وجلب البيانات، بل حتى الأُطر الوصفية المبنية فوقها — صيغةً تستطيع التفكير فيها بدل حيلةٍ عليك حفظها.