ما المقصود بمفهوم رفع الحالة للأعلى في React؟

دقائق القراءة: 5

مقدمة: ما معنى رفع الحالة للأعلى في React؟

يُعد مفهوم Lifting State Up من الأنماط الأساسية التي ينبغي على كل مطور يستخدم React فهمها جيداً. والفكرة ببساطة هي نقل state من مكوّن فرعي إلى أقرب مكوّن أب مشترك، بحيث تتمكن عدة مكونات من قراءة البيانات نفسها أو تحديثها دون الحاجة إلى حلول أكثر تعقيداً.

هذا النمط مفيد جداً عندما يكون لديك أكثر من مكوّن يعتمد على البيانات نفسها، مثل عدّاد يعرض عدد العناصر، وقائمة تعرض تلك العناصر، ونموذج يضيف عناصر جديدة إليها.

شرح مفهوم رفع الحالة للأعلى في React باستخدام تطبيق مهام بسيط

تفكيك مثال عملي: تطبيق مهام بسيط

لفهم الفكرة بشكل واضح، لنفترض أننا نبني تطبيق مهام بسيط يتكون من ثلاثة مكونات رئيسية:

  • TodoCount لعرض العدد الإجمالي للمهام.
  • TodoList لعرض قائمة المهام الحالية.
  • AddTodo لإضافة مهمة جديدة عبر نموذج إدخال.

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

الكود الأولي للتطبيق

// src/App.js
import React from "react";

export default function App() {
  return (
    <>
      <TodoCount />
      <TodoList />
      <AddTodo />
    </>
  );
}

function TodoCount() {
  return <div>Total Todos:</div>;
}

function TodoList() {
  const [todos, setTodos] = React.useState([
    "item 1",
    "item 2",
    "item 3"
  ]);

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo}>{todo}</li>
      ))}
    </ul>
  );
}

function AddTodo() {
  function handleSubmit(event) {
    event.preventDefault();
    const todo = event.target.elements.todo.value;
    console.log(todo);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" id="todo" />
      <button type="submit">Add Todo</button>
    </form>
  );
}

لماذا تحتاج إلى رفع الحالة للأعلى؟

المشكلة هنا أن البيانات موجودة داخل TodoList فقط، بينما كل من TodoCount وAddTodo يحتاجان أيضاً إلى التعامل مع هذه البيانات:

  • TodoCount يحتاج إلى معرفة عدد المهام.
  • AddTodo يحتاج إلى تحديث القائمة عند إضافة مهمة جديدة.
  • TodoList يحتاج بالطبع إلى عرض المهام.

عندما تحتاج عدة مكونات شقيقة إلى الحالة نفسها، فإن الحل الأنسب غالباً هو نقل تلك الحالة إلى المكوّن الأب المشترك بينها. وفي هذا المثال، يكون المكوّن المناسب هو App.

نقل state إلى المكوّن الأب App

// src/App.js
import React from "react";

export default function App() {
  const [todos, setTodos] = React.useState([
    "item 1",
    "item 2",
    "item 3"
  ]);

  return (
    <>
      <TodoCount />
      <TodoList />
      <AddTodo />
    </>
  );
}

function TodoCount() {
  return <div>Total Todos:</div>;
}

function TodoList() {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo}>{todo}</li>
      ))}
    </ul>
  );
}

function AddTodo() {
  function handleSubmit(event) {
    event.preventDefault();
    const todo = event.target.elements.todo.value;
    console.log(todo);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" id="todo" />
      <button type="submit">Add Todo</button>
    </form>
  );
}

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

تمرير الحالة إلى الأسفل باستخدام props

بما أن TodoList وTodoCount يحتاجان إلى قراءة البيانات، يمكننا تمرير مصفوفة todos إليهما عبر props.

تمرير الحالة من مكون App إلى المكونات الفرعية في React باستخدام props

تحديث TodoList وTodoCount لقراءة البيانات

import React from "react";

export default function App() {
  const [todos, setTodos] = React.useState([
    "item 1",
    "item 2",
    "item 3"
  ]);

  return (
    <>
      <TodoCount todos={todos} />
      <TodoList todos={todos} />
      <AddTodo />
    </>
  );
}

function TodoCount({ todos }) {
  return <div>Total Todos: {todos.length}</div>;
}

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo}>{todo}</li>
      ))}
    </ul>
  );
}

بهذا الشكل، أصبح المكوّن TodoList يعرض العناصر من جديد، وأصبح المكوّن TodoCount قادراً على إظهار العدد الإجمالي للمهام من خلال todos.length.

تمرير دوال التحديث إلى الأسفل

الخطوة الأخيرة هي تمكين المكوّن AddTodo من إضافة عنصر جديد إلى القائمة. هنا لا نحتاج إلى تمرير الحالة كاملة، بل يكفي تمرير دالة التحديث setTodos.

الفكرة المهمة هنا أن المكوّن الفرعي لا يملك الحالة، لكنه يملك وسيلة آمنة لتحديثها عبر دالة تأتيه من المكوّن الأب.

استخدام setTodos لإضافة مهمة جديدة

import React from "react";

export default function App() {
  const [todos, setTodos] = React.useState([
    "item 1",
    "item 2",
    "item 3"
  ]);

  return (
    <>
      <TodoCount todos={todos} />
      <TodoList todos={todos} />
      <AddTodo setTodos={setTodos} />
    </>
  );
}

function AddTodo({ setTodos }) {
  function handleSubmit(event) {
    event.preventDefault();
    const todo = event.target.elements.todo.value;
    setTodos(prevTodos => [...prevTodos, todo]);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" id="todo" />
      <button type="submit">Add Todo</button>
    </form>
  );
}

نلاحظ هنا استخدام الصيغة الدالية داخل setTodos بالشكل prevTodos => [...prevTodos, todo]. هذه الطريقة مفضلة لأنها تعتمد على آخر قيمة مؤكدة للحالة، وهو ما يجعل التحديث أكثر موثوقية، خاصة عند وجود تحديثات متعددة ومتقاربة.

كيف يعمل هذا النمط عملياً داخل التطبيق؟

عند إرسال النموذج داخل AddTodo، يتم التقاط القيمة من حقل الإدخال، ثم تُضاف إلى المصفوفة الحالية. وبمجرد تحديث الحالة داخل App، تعيد React تصيير المكونات التي تعتمد على هذه البيانات:

  • يعيد TodoList عرض القائمة مع العنصر الجديد.
  • يعيد TodoCount حساب العدد الإجمالي تلقائياً.

هذه إحدى أقوى مزايا رفع الحالة للأعلى: مصدر بيانات واحد، وسلوك متوقع، وتدفق واضح للبيانات من الأعلى إلى الأسفل.

إضافة مهمة جديدة بعد تطبيق مفهوم رفع الحالة للأعلى في React

متى يكون رفع الحالة للأعلى هو الحل المناسب؟

يفضل استخدام Lifting State Up عندما:

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

مزايا هذا الأسلوب

  • تبسيط إدارة البيانات داخل التطبيق.
  • تقليل التكرار والتعارض بين الحالات المتعددة.
  • جعل تدفق البيانات أوضح وأسهل في الصيانة.
  • الاستفادة من props وواجهات React الأساسية دون تعقيد.

متى لا يكون كافياً؟

إذا أصبحت شجرة المكونات كبيرة جداً، وبدأت عملية تمرير props عبر مستويات متعددة في إحداث تعقيد، فقد يكون من المناسب التفكير في حلول مثل React Context أو مكتبات إدارة الحالة مثل Redux. لكن في التطبيقات الصغيرة والمتوسطة، غالباً ما يكون رفع الحالة للأعلى كافياً وفعالاً.

أفضل الممارسات عند استخدام رفع الحالة للأعلى

  • ضع الحالة في أقرب مكوّن أب مشترك فقط، ولا ترفعها أكثر من اللازم.
  • مرّر البيانات التي تحتاجها المكونات فعلاً، وليس كل شيء.
  • مرّر دوال التحديث بوضوح إلى المكونات التي تحتاج إلى تعديل الحالة.
  • استخدم الأسماء الواضحة مثل todos وsetTodos لتسهيل قراءة الكود.
  • اعتمد على التحديثات الدالية عندما تكون الحالة الجديدة مبنية على الحالة السابقة.

الخلاصة التقنية

يُعد Lifting State Up من أهم الأنماط العملية في React لأنه يوفّر طريقة بسيطة ومنطقية لمشاركة الحالة بين المكونات الشقيقة. بدلاً من اللجوء مباشرة إلى أدوات متقدمة لإدارة الحالة، يمكنك في كثير من الحالات الاكتفاء بنقل state إلى المكوّن الأب المشترك، ثم تمرير القيم ودوال التحديث عبر props. هذا النهج يجعل التطبيق أوضح بنيوياً وأسهل في التطوير والصيانة، وهو خيار ذكي لكل مطور يسعى إلى كتابة واجهات متماسكة وقابلة للتوسع.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *