آشنایی با ارتباط کامپوننت‌ها در React

یکی از مهم‌ترین مفاهیم React این است که کامپوننت‌ها چطور با هم ارتباط برقرار می‌کنند. در ابتدا این موضوع می‌تواند گیج‌کننده باشد:

  • چرا تابع را به عنوان prop پاس می‌دهیم؟
  • چرا state مال والد است؟
  • چرا فرزند نمی‌تواند داده والد را مستقیم تغییر دهد؟

وقتی معماری جریان داده React را بفهمیم، جواب خیلی روشن‌تر می‌شود.

اصل اساسی React

React از جریان داده یک‌طرفه پیروی می‌کند: والد ← فرزند. داده از طریق props به پایین می‌رود و اکشن‌ها از طریق توابع Callback به بالا می‌آیند. راه ساده برای به خاطر سپردن:

Data Down ↓
Actions Up ↑

سناریوی مثال

فهرستی از کاربران را تصور کنید. کامپوننت والد آرایه کاربران را نگه می‌دارد و چند کامپوننت فرزند رندر می‌کند که هر کدام یک دکمه حذف دارند. با کلیک روی دکمه، فرزند یک اکشن را فعال می‌کند، والد state را به‌روز می‌کند و 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>
  );
}

واقعاً چه اتفاقی می‌افتد؟

قدم ۱ — والد مالک state است

آرایه کاربران داخل کامپوننت والد است: const [users, setUsers] = useState(...). والد منبع حقیقت است.

قدم ۲ — والد داده را به پایین می‌فرستد

والد با propsهایی مثل id={user.id} و name={user.name} داده را به فرزند می‌فرستد. حالا فرزند می‌تواند اطلاعات را نمایش دهد.

قدم ۳ — والد یک تابع را به پایین می‌فرستد

والد یک تابع هم می‌فرستد: handleDelete={deleteUser}. به این Callback Prop می‌گویند.

قدم ۴ — فرزند می‌تواند اول منطق خودش را اجرا کند

این همان بخشی است که خیلی از تازه‌کارهای React نمی‌بینند. فرزند می‌تواند پیش از صدا زدن تابع والد، انیمیشن، state محلی، افکت هاور، چشمک یا نشانگر بارگذاری داشته باشد. اول state خودش را با setIsFlashing(true) به‌روز می‌کند و بعد اکشن والد را با handleDelete(id) فعال می‌کند:

Child local logic
   ↓
Callback prop
   ↓
Parent state update

قدم ۵ — والد state را به‌روز می‌کند

والد درخواست را دریافت می‌کند و state کاربران را با setUsers(prev => prev.filter(user => user.id !== id)) به‌روز می‌کند. React خودکار دوباره رندر می‌کند و کاربر حذف‌شده ناپدید می‌شود.

چرا این الگو مهم است؟

اپلیکیشن‌های React را قابل پیش‌بینی، قابل استفاده دوباره، ساده‌تر برای دیباگ و ساده‌تر برای گسترش نگه می‌دارد. فرزند مسئول رابط کاربری، کلیک‌ها، تعامل‌های محلی و انیمیشن‌هاست. والد مسئول state مشترک، منطق کسب‌وکار و به‌روزرسانی داده است.

state محلی در برابر state مشترک

سؤال مهم دیگر این است که state کجا باشد.

state محلی — اگر فقط یک کامپوننت به آن نیاز دارد (const [isLiked, setIsLiked])، داخل فرزند نگهش دارید. مثال‌ها: هاور، انیمیشن، تاگل، افکت‌های محلی رابط کاربری.

state مشترک — اگر چند کامپوننت به یک داده وابسته‌اند (const [users, setUsers])، آن را به والد منتقل کنید. مثال‌ها: فهرست کاربران، سبد خرید، تم، اعلان‌ها، شمارش‌ها. به این الگو بالا بردن state (Lifting State Up) می‌گویند.

مدل ذهنی نهایی

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

وقتی این الگو جا بیفتد، فهم ارتباط کامپوننت‌ها در React خیلی ساده‌تر می‌شود.