فهم التواصل بين المكونات في React

من أهم مفاهيم React فهم كيفية تواصل المكونات مع بعضها. قد يبدو هذا مربكًا في البداية:

  • لماذا نمرّر الدوال كخصائص؟
  • لماذا يملك المكوّن الأب الحالة؟
  • لماذا لا يستطيع الابن تغيير بيانات الأب مباشرة؟

يصبح الجواب أوضح بكثير عندما نفهم معمارية تدفق البيانات في React.

المبدأ الأساسي في React

يتبع React تدفق بيانات أحادي الاتجاه: الأب ← الابن. تنتقل البيانات للأسفل عبر الخصائص، وتنتقل الأفعال للأعلى عبر دوال الاستدعاء. طريقة بسيطة للتذكر:

Data Down ↓
Actions Up ↑

سيناريو المثال

تخيّل قائمة مستخدمين. يحتفظ المكوّن الأب بمصفوفة المستخدمين ويعرض عدة مكونات أبناء، لكل منها زر حذف. عند النقر يطلق الابن فعلًا، فيحدّث الأب الحالة، ويعيد React عرض الواجهة.

المكوّن الأب

import { useState } from "react";
import UserCard from "./UserCard";

export default function App() {
  const [users, setUsers] = useState([
    { id: 1, name: "Parisa" },
    { id: 2, name: "Alex" },
  ]);

  const deleteUser = (id: number) => {
    setUsers(prev => prev.filter(user => user.id !== id));
  };

  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          id={user.id}
          name={user.name}
          handleDelete={deleteUser}
        />
      ))}
    </div>
  );
}

المكوّن الابن

import { useState } from "react";

type Props = {
  id: number;
  name: string;
  handleDelete: (id: number) => void;
};

export default function UserCard({ id, name, handleDelete }: Props) {
  const [isFlashing, setIsFlashing] = useState(false);

  const handleDeleteClick = () => {
    // Local UI logic inside the child
    setIsFlashing(true);

    // Then trigger the parent action
    setTimeout(() => {
      handleDelete(id);
    }, 300);
  };

  return (
    <div style={{ backgroundColor: isFlashing ? "#999" : "#fff", transition: "0.3s" }}>
      <p>{name}</p>
      <button onClick={handleDeleteClick}>Delete</button>
    </div>
  );
}

ماذا يحدث فعلًا؟

الخطوة 1 — الأب يملك الحالة

مصفوفة المستخدمين موجودة داخل المكوّن الأب: const [users, setUsers] = useState(...). الأب هو مصدر الحقيقة.

الخطوة 2 — الأب يمرّر البيانات للأسفل

يرسل الأب البيانات إلى الابن بخصائص مثل id={user.id} وname={user.name}. الآن يستطيع الابن عرض المعلومات.

الخطوة 3 — الأب يمرّر دالة للأسفل

يرسل الأب دالة أيضًا: handleDelete={deleteUser}. وتُسمى خاصية استدعاء (Callback Prop).

الخطوة 4 — يمكن للابن تنفيذ منطقه أولًا

هذا الجزء يغفل عنه كثير من المبتدئين. يمكن للابن امتلاك حركات وحالة محلية وتأثيرات تمرير ووميض ومؤشرات تحميل قبل استدعاء دالة الأب. يحدّث حالته أولًا بـ setIsFlashing(true)، ثم يطلق فعل الأب بـ handleDelete(id):

Child local logic
   ↓
Callback prop
   ↓
Parent state update

الخطوة 5 — الأب يحدّث الحالة

يتلقى الأب الطلب ويحدّث حالة المستخدمين بـ setUsers(prev => prev.filter(user => user.id !== id)). يعيد React العرض تلقائيًا ويختفي المستخدم المحذوف.

لماذا هذا النمط مهم؟

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

الحالة المحلية مقابل الحالة المشتركة

سؤال مهم آخر هو أين يجب أن تعيش الحالة.

الحالة المحلية — إذا احتاجها مكوّن واحد فقط (const [isLiked, setIsLiked])، فأبقها داخل الابن. أمثلة: التمرير، والحركة، والتبديل، وتأثيرات الواجهة المحلية.

الحالة المشتركة — إذا اعتمدت عدة مكونات على البيانات نفسها (const [users, setUsers])، فانقلها إلى الأب. أمثلة: قوائم المستخدمين، وعناصر السلة، والسمات، والإشعارات، والإجماليات. يُسمى هذا النمط رفع الحالة (Lifting State Up).

النموذج الذهني النهائي

Parent
├── State
├── Logic
└── Functions
      ↓ props
Child
├── UI
├── Local State
├── Events
└── Execute callbacks

عندما يترسخ هذا النمط، يصبح فهم التواصل بين المكونات في React أسهل بكثير.