دليل React الشامل: تعلّم المفاهيم الأساسية عبر بناء تطبيق مهام عملي
إذا كنت تبحث عن طريقة عملية لتعلّم React دون الدخول في شروحات نظرية طويلة، فهذا الدليل صُمم لهذا الهدف تحديداً. سنبني تطبيق قائمة مهام بسيطاً، لكن أثناء ذلك سنتعرّف على أهم مفاهيم React التي تحتاجها فعلاً في المشاريع الواقعية، مثل JSX، والمكوّنات، وprops، والنماذج، وإدارة الحالة، وhooks، والتعامل مع الأحداث، وتحديث الواجهة بشكل تفاعلي.
الميزة الأهم هنا أن التعلّم سيكون بالممارسة المباشرة. بدلاً من حفظ المفاهيم بشكل منفصل، ستراها تعمل داخل مشروع حقيقي خطوة بخطوة.
لماذا يُعد هذا المشروع مدخلاً ممتازاً لتعلّم React؟
تطبيق قائمة المهام يبدو بسيطاً، لكنه يجمع معظم الأساسيات التي يعتمد عليها أي تطبيق حديث مبني بـ React. فمن خلاله ستتعلّم كيف:
- تنشئ واجهة باستخدام
JSX. - تقسّم التطبيق إلى مكوّنات قابلة لإعادة الاستخدام.
- تمرّر البيانات بين المكوّنات عبر
props. - تتعامل مع القوائم باستخدام
map(). - تحدّث البيانات ديناميكياً عبر
useState. - تقرأ إدخالات المستخدم من النماذج.
- تستخدم
useRefللوصول إلى عناصرDOM. - تنفّذ عمليات الإنشاء والقراءة والتحديث والحذف، أو ما يُعرف بـ
CRUD.
بدء مشروع React بسرعة
لبدء التطبيق دون تثبيت أدوات محلية، يمكن استخدام موقع react.new، والذي يفتح لك بيئة جاهزة داخل CodeSandbox. بهذه الطريقة تستطيع كتابة الكود ومعاينة النتيجة فوراً من داخل المتصفح.

نصيحة سريعة: احرص على حفظ المشروع باستخدام Ctrl + S أو Command + S حتى تحصل على رابط خاص يمكنك الرجوع إليه لاحقاً.
عند فتح المشروع ستجد أن المكوّن المعروض افتراضياً هو App، ويتم استيراده ثم عرضه داخل الملف index.js.
// src/index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
rootElement
);
وظيفة هذا الكود هي عرض التطبيق داخل العنصر الأساسي الموجود في ملف index.html، وهو العنصر الذي يحمل المعرّف root.
فهم JSX في React
بعد تشغيل المشروع، نبدأ ببناء الواجهة من داخل المكوّن App. في React نستخدم JSX لوصف شكل الواجهة. وهو يشبه HTML بصرياً، لكنه في الحقيقة صياغة تُكتب داخل JavaScript.

مثال بسيط:
// src/App.js
import "./styles.css";
export default function App() {
return (
<div className="App">
<h1>Todo List</h1>
</div>
);
}
لاحظ هنا استخدام الخاصية className بدلاً من class. في JSX توجد فروق بسيطة عن HTML التقليدي، ومن أهمها أن أسماء بعض الخصائص تُكتب بأسلوب camelCase.

إنشاء قائمة مهام أولية
يمكننا عرض قائمة مهام أولية بإضافة عنصر ul وتحته عنصر li:
// src/App.js
import "./styles.css";
export default function App() {
return (
<div className="App">
<h1>Todo List</h1>
<ul>
<li>Todo Item</li>
</ul>
</div>
);
}
لكن هذا الحل محدود. في التطبيقات الحقيقية نحتاج إلى تقسيم الواجهة إلى مكوّنات مستقلة ليسهل توسيعها وصيانتها.
المكوّنات في React: أساس بناء الواجهة
المكوّنات هي حجر الأساس في React. كل جزء من الواجهة يمكن تحويله إلى مكوّن مستقل، وهذا يمنحك فوائد مهمة:
- إعادة استخدام الكود.
- تنظيم منطق التطبيق بشكل أفضل.
- فصل المسؤوليات بين أجزاء الواجهة.
- سهولة الاختبار والتطوير مستقبلاً.
بدلاً من كتابة كل شيء داخل App، سننشئ مكوّناً خاصاً لعرض المهام باسم TodoList.
// src/App.js
import "./styles.css";
export default function App() {
return (
<div className="App">
<h1>Todo List</h1>
<TodoList />
</div>
);
}
قواعد مهمة عند إنشاء المكوّنات
- يجب أن يبدأ اسم المكوّن بحرف كبير مثل
AppأوTodoList. - إذا كان العنصر أو المكوّن لا يحتوي على أبناء، فيجب إغلاقه ذاتياً مثل
<TodoList />. - كل مكوّن يجب أن يعيد عناصر
JSXعبرreturn.
إذا أنشأت مكوّناً ولم تُرجع منه شيئاً، فستظهر لك رسالة خطأ لأن React يتوقع واجهة قابلة للعرض.

إعداد بيانات المهام وتمريرها إلى المكوّنات
لنبدأ بإنشاء مصفوفة تحتوي على بعض المهام التجريبية:
// src/App.js
import "./styles.css";
export default function App() {
const todos = [
{ id: 1, text: "Wash dishes", done: false },
{ id: 2, text: "Do laundry", done: false },
{ id: 3, text: "Take shower", done: false }
];
return (
<div>
<h1>Todo List</h1>
<TodoList />
</div>
);
}
function TodoList() {}
لكن كيف نجعل المكوّن TodoList يرى هذه البيانات؟ هنا يأتي دور props.
ما هي Props ولماذا نستخدمها؟
props هي طريقة تمرير البيانات من مكوّن أب إلى مكوّن ابن. فكّر فيها كأنها معاملات تُرسل إلى الدوال، لكن داخل مكوّنات React.
// src/App.js
import "./styles.css";
export default function App() {
const todos = [
{ id: 1, text: "Wash dishes", done: false },
{ id: 2, text: "Do laundry", done: false },
{ id: 3, text: "Take shower", done: false }
];
return (
<div>
<h1>Todo List</h1>
<TodoList todos={todos} />
</div>
);
}
function TodoList(props) {
console.log(props);
}
عند تمرير القيمة todos={todos}، يصبح لدينا داخل المكوّن الابن كائن props يحتوي على الخاصية todos.
عرض عناصر المصفوفة باستخدام map()
لعرض كل مهمة داخل القائمة، نستخدم الدالة map() على المصفوفة، ثم نعيد عنصراً من JSX لكل مهمة:


لنجعل الكود أوضح باستخدام تفكيك الكائنات object destructuring:
// src/App.js
import "./styles.css";
export default function App() {
const todos = [
{ id: 1, text: "Wash dishes", done: false },
{ id: 2, text: "Do laundry", done: false },
{ id: 3, text: "Take shower", done: false }
];
return (
<div>
<h1>Todo List</h1>
<TodoList todos={todos} />
</div>
);
}
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
أهمية الخاصية key في React
عند عرض قائمة عناصر، يطلب React تزويد كل عنصر بقيمة key فريدة. هذه القيمة تساعده على تتبّع ترتيب العناصر ومعرفة ما الذي تغيّر عند إعادة التصيير.

أفضل خيار عادةً هو استخدام معرّف فريد مثل todo.id. من دون key مناسب، قد تظهر مشكلات في التحديث أو الأداء.
إضافة مكوّن لإدخال مهام جديدة
الآن نحتاج إلى مكوّن يسمح للمستخدم بإضافة مهمة جديدة. سننشئ مكوّناً باسم AddTodo يحتوي على نموذج بسيط:
// src/App.js
import "./styles.css";
export default function App() {
const todos = [
{ id: 1, text: "Wash dishes", done: false },
{ id: 2, text: "Do laundry", done: false },
{ id: 3, text: "Take shower", done: false }
];
return (
<div>
<h1>Todo List</h1>
<TodoList todos={todos} />
<AddTodo />
</div>
);
}
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
function AddTodo() {
return (
<form>
<input placeholder="Add todo" />
<button type="submit">Submit</button>
</form>
);
}
تذكّر أن عناصر مثل input يجب إغلاقها ذاتياً داخل JSX، وإلا فستظهر لك أخطاء ترجمة.
التعامل مع إرسال النموذج في React
لكي نلتقط عملية الإرسال، نستخدم الحدث onSubmit على العنصر form، ثم نربطه بدالة مثل handleAddTodo.
// src/App.js
import "./styles.css";
function AddTodo() {
function handleAddTodo() {}
return (
<form onSubmit={handleAddTodo}>
<input placeholder="Add todo" />
<button type="submit">Submit</button>
</form>
);
}
في React من الشائع تسمية الدوال المرتبطة بالأحداث ببادئة handle، مثل handleClick وhandleSubmit. هذه ليست قاعدة إلزامية، لكنها ممارسة جيدة لتوضيح الغرض من الدالة.
منع السلوك الافتراضي للنموذج
عند إرسال النموذج، يعيد المتصفح تحميل الصفحة افتراضياً. في تطبيقات React لا نريد هذا السلوك، لذلك نستخدم event.preventDefault().
// src/App.js
import "./styles.css";
function AddTodo() {
function handleAddTodo(event) {
event.preventDefault();
}
return (
<form onSubmit={handleAddTodo}>
<input placeholder="Add todo" />
<button type="submit">Submit</button>
</form>
);
}
قراءة قيمة الحقل من داخل النموذج
بعد منع إعادة التحميل، نحتاج إلى الوصول للنص الذي أدخله المستخدم. يمكن فعل ذلك عبر event.target.elements. ولتسهيل الوصول إلى الحقل، نضيف له الخاصية name.


// src/App.js
import "./styles.css";
function AddTodo() {
function handleAddTodo(event) {
event.preventDefault();
const text = event.target.elements.addTodo.value;
const todo = {
id: 4,
text,
done: false
};
}
return (
<form onSubmit={handleAddTodo}>
<input name="addTodo" placeholder="Add todo" />
<button type="submit">Submit</button>
</form>
);
}
هنا أنشأنا كائناً جديداً يمثل المهمة الجديدة، لكن لا يزال هناك سؤال أساسي: كيف نضيفه فعلياً إلى القائمة ونجعل الواجهة تتحدّث تلقائياً؟
مقدمة إلى الحالة state في React
المصفوفة التي أنشأناها سابقاً كانت بيانات ثابتة. حتى لو عدّلناها يدوياً، فلن يفهم React بالضرورة أنه يجب إعادة عرض الواجهة.
لهذا نستخدم state. الحالة هي أسلوب React في إدارة البيانات التي يمكن أن تتغير بمرور الوقت، وربط هذه التغييرات بتحديث الواجهة تلقائياً.
إدارة الحالة باستخدام useState
نستورد الخطاف useState من React، ثم نستخدمه داخل المكوّن App لتحويل المصفوفة إلى حالة ديناميكية:

// src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [todos, setTodos] = useState([
{ id: 1, text: "Wash dishes", done: false },
{ id: 2, text: "Do laundry", done: false },
{ id: 3, text: "Take shower", done: false }
]);
return (
<div>
<h1>Todo List</h1>
<TodoList todos={todos} />
<AddTodo setTodos={setTodos} />
</div>
);
}
الدالة useState تعيد لنا قيمتين:
- متغير الحالة نفسه، وهو هنا
todos. - دالة لتحديث هذه الحالة، وهي
setTodos.
بما أن مكوّن AddTodo هو المسؤول عن إضافة المهام، فسنمرّر إليه الدالة setTodos.

// src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [todos, setTodos] = useState([
{ id: 1, text: "Wash dishes", done: false },
{ id: 2, text: "Do laundry", done: false },
{ id: 3, text: "Take shower", done: false }
]);
return (
<div>
<h1>Todo List</h1>
<TodoList todos={todos} />
<AddTodo setTodos={setTodos} />
</div>
);
}
function AddTodo({ setTodos }) {
function handleAddTodo(event) {
event.preventDefault();
const text = event.target.elements.addTodo.value;
const todo = {
id: 4,
text,
done: false
};
setTodos((prevTodos) => {
return prevTodos.concat(todo);
});
}
return (
<form onSubmit={handleAddTodo}>
<input name="addTodo" placeholder="Add todo" />
<button type="submit">Submit</button>
</form>
);
}
استخدام الصيغة setTodos((prevTodos) => ...) أفضل من الاعتماد المباشر على القيمة الحالية في كثير من الحالات، لأنه يضمن أنك تعمل على أحدث نسخة من الحالة.
كيف تعمل إعادة التصيير في React؟
عند تحديث الحالة، يقوم React بإعادة تصيير المكوّن الذي تغيّرت حالته، ثم يعيد تصيير المكوّنات الأبناء المرتبطة به. لهذا، بمجرد إضافة مهمة جديدة، ستظهر مباشرة داخل TodoList دون أي تحديث يدوي للواجهة.

تنظيف حقل الإدخال باستخدام useRef
بعد إضافة مهمة، من الأفضل تفريغ حقل الإدخال تلقائياً. هنا نستخدم useRef للوصول المباشر إلى عنصر input داخل DOM.
// src/App.js
import React, { useState } from "react";
import "./styles.css";
function AddTodo({ setTodos }) {
const inputRef = React.useRef();
function handleAddTodo(event) {
event.preventDefault();
const text = event.target.elements.addTodo.value;
const todo = {
id: 4,
text,
done: false
};
setTodos((prevTodos) => {
return prevTodos.concat(todo);
});
inputRef.current.value = "";
}
return (
<form onSubmit={handleAddTodo}>
<input
name="addTodo"
placeholder="Add todo"
ref={inputRef}
/>
<button type="submit">Submit</button>
</form>
);
}

قواعد أساسية لاستخدام Hooks
- معظم الخطافات تبدأ بكلمة
useمثلuseStateوuseRef. - يجب استدعاؤها في أعلى المكوّن الوظيفي.
- لا تُستخدم داخل الشروط مثل
if. - لا تُستخدم داخل المكوّنات الصنفية
class components.
تمييز المهمة كمكتملة باستخدام onClick أو onDoubleClick
الخطوة التالية هي السماح للمستخدم بتمييز المهمة كمكتملة. يمكن تنفيذ ذلك بتغيير قيمة done من false إلى true، ثم تطبيق تنسيق بصري مثل شطب النص.
في JSX تُكتب الأنماط المضمنة عبر كائن JavaScript داخل الخاصية style، وليس كنص عادي كما في HTML.


// src/App.js
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li
style={{ textDecoration: todo.done ? "line-through" : "" }}
key={todo.id}
>
{todo.text}
</li>
))}
</ul>
);
}
لكننا لا نريد شطب كل المهام، بل فقط المهام المكتملة. لهذا استخدمنا شرطاً ثلاثياً ternary لفحص قيمة todo.done.
تبديل حالة المهمة عند النقر
سنمرّر أيضاً setTodos إلى المكوّن TodoList حتى يتمكن من تحديث الحالة:
// src/App.js
function TodoList({ todos, setTodos }) {
function handleToggleTodo(todo) {
const updatedTodos = todos.map((t) =>
t.id === todo.id ? { ...t, done: !t.done } : t
);
setTodos(updatedTodos);
}
return (
<ul>
{todos.map((todo) => (
<li
onDoubleClick={() => handleToggleTodo(todo)}
style={{ textDecoration: todo.done ? "line-through" : "" }}
key={todo.id}
>
{todo.text}
<DeleteTodo todo={todo} setTodos={setTodos} />
</li>
))}
</ul>
);
}
استخدمنا هنا onDoubleClick بدلاً من onClick لتقليل التفعيل غير المقصود. كما اعتمدنا على العامل spread عبر {...t} لإنشاء نسخة جديدة من العنصر مع تعديل الخاصية done فقط.

ومن الأفضل أن يكون لكل مهمة معرّف فريد حقاً. لذلك عند إنشاء المهام الجديدة يمكن استخدام Math.random() أو أي أسلوب أفضل لتوليد المعرّفات.
حذف المهام بإضافة مكوّن مستقل
لإكمال وظائف التطبيق، سنضيف إمكانية حذف المهمة. بدلاً من وضع منطق الحذف داخل TodoList مباشرة، سنفصل ذلك في مكوّن جديد باسم DeleteTodo.

// src/App.js
function DeleteTodo({ todo, setTodos }) {
function handleDeleteTodo() {
const confirmed = window.confirm("Do you want to delete this?");
if (confirmed) {
setTodos((prevTodos) => {
return prevTodos.filter((t) => t.id !== todo.id);
});
}
}
return (
<span
onClick={handleDeleteTodo}
role="button"
style={{
color: "red",
fontWeight: "bold",
marginLeft: 10,
cursor: "pointer"
}}
>
x
</span>
);
}
لاحظ استخدام role="button" لأن العنصر span ليس زرّاً بطبيعته، لكننا نستخدمه هنا كسلوك تفاعلي. كما أن الحذف لا يتم مباشرة، بل بعد تأكيد المستخدم عبر window.confirm().
إظهار رسالة عند خلو القائمة
إذا تم حذف جميع المهام، فمن الأفضل إظهار رسالة واضحة بدلاً من عرض مساحة فارغة. يمكن تنفيذ ذلك بسهولة عبر شرط قبل إعادة JSX:
// src/App.js
function TodoList({ todos, setTodos }) {
function handleToggleTodo(todo) {
const updatedTodos = todos.map((t) =>
t.id === todo.id ? { ...t, done: !t.done } : t
);
setTodos(updatedTodos);
}
if (!todos.length) {
return <p>No todos left!</p>;
}
return (
<ul>
{todos.map((todo) => (
<li
onDoubleClick={() => handleToggleTodo(todo)}
style={{ textDecoration: todo.done ? "line-through" : "" }}
key={todo.id}
>
{todo.text}
<DeleteTodo todo={todo} setTodos={setTodos} />
</li>
))}
</ul>
);
}
النسخة النهائية للتطبيق
بعد جمع كل الأجزاء السابقة، يصبح لدينا تطبيق مهام بسيط لكنه متكامل من ناحية المفاهيم الأساسية. التطبيق يدعم:
- عرض المهام الحالية.
- إضافة مهمة جديدة.
- تحديث حالة المهمة إلى مكتملة أو غير مكتملة.
- حذف المهمة بعد التأكيد.
وهذا يعني أنك طبّقت عملياً نموذج CRUD الكامل داخل مشروع React صغير وواضح.
أهم المفاهيم التي تعلّمتها من هذا المشروع
- بناء الواجهات باستخدام
JSX. - تقسيم التطبيق إلى مكوّنات مستقلة.
- تمرير البيانات عبر
props. - عرض القوائم باستخدام
map(). - استخدام
keyلتحسين تتبّع العناصر. - التعامل مع الأحداث مثل
onSubmitوonClickوonDoubleClick. - إدارة البيانات المتغيرة باستخدام
useState. - الوصول المباشر إلى عناصر الإدخال باستخدام
useRef. - استخدام الشروط والتنسيقات المضمنة بشكل صحيح داخل
React.
نصائح عملية لتطوير هذا المشروع لاحقاً
إذا أردت تحويل هذا التطبيق من مثال تعليمي إلى مشروع أقرب للواقع، ففكّر في الإضافات التالية:
- حفظ المهام في
localStorageحتى لا تختفي بعد تحديث الصفحة. - إضافة فلاتر لعرض المهام المكتملة أو غير المكتملة فقط.
- منع إضافة مهمة فارغة عبر التحقق من النص قبل الحفظ.
- استبدال
Math.random()بآلية أكثر موثوقية لتوليد المعرفات. - فصل كل مكوّن في ملف مستقل لتحسين قابلية التوسعة.
- إضافة تنسيقات أفضل وتجربة استخدام أوضح على الجوال.
الخلاصة التقنية
هذا المشروع يبرهن أن تعلّم React لا يحتاج إلى تطبيقات ضخمة في البداية، بل إلى مثال عملي ذكي يجمع بين المفاهيم الأساسية في سياق واحد. بناء تطبيق مهام بسيط يمنحك فهماً حقيقياً لكيفية عمل state وprops وhooks وتدفّق البيانات داخل المكوّنات. من الناحية التقنية، أفضل ما في هذا النموذج أنه يرسّخ مبدأ فصل المسؤوليات، ويُظهر كيف يمكن لتطبيق صغير أن يتحول إلى قاعدة صلبة لبناء تطبيقات أكثر احترافية لاحقاً.