كيفية استخدام useEffect و useState بشكل صحيح في React

آخر تحديث: 02/12/2026
نبذة عن الكاتب: ج مصدر تريل
  • فهم كيفية قيام useState بالحفاظ على حالة المكون المحلي وتحديثها، بما في ذلك التحديثات الوظيفية ومعالجة الكائنات.
  • استخدم useEffect للتأثيرات الجانبية مع منطق إعداد/تنظيف واضح ومصفوفات تبعية دقيقة لتجنب التسريبات والحلقات.
  • قم بدمج useState و useEffect لمهام العالم الحقيقي مثل جلب البيانات والاشتراكات وتحديثات DOM في مكونات الوظائف.
  • اتبع قواعد الخطافات وتعامل مع التأثيرات كعمليات "بعد العرض" للحفاظ على مكونات React قابلة للتنبؤ والصيانة.

خطافات React useState و useEffect

لقد غيرت خطافات React تمامًا طريقة كتابة المكونات، وإتقان useState و useEffect يُعدّ هذا الدليل بمثابة بوابة الدخول لكتابة أكواد React الحديثة. إذا كنت تستخدمها بالفعل ولكنك لا تزال تواجه مشكلات مثل الحلقات اللانهائية، أو حالة البيانات غير المكتملة، أو مصفوفات التبعيات المعقدة، فسيساعدك هذا الدليل على ربط جميع العناصر المفقودة بطريقة عملية.

سنتناول في هذه المقالة بالتفصيل كيفية الاستخدام الصحيح useState و useEffect سويا، لماذا تم إدخال الخطافات في المقام الأول، والقواعد الرسمية والتحذيرات، وكيف تعمل التبعيات حقًا من الداخل، والمزالق الشائعة التي تكسر مكوناتك، والأنماط التي تم اختبارها في المعارك للآثار الجانبية والتنظيف وإدارة الحالة في المشاريع الحقيقية.

لماذا نستخدم الخطافات، ولماذا تحديداً نستخدم useState و useEffect؟

تمت إضافة الخطافات في React 16.8 للسماح لمكونات الوظائف باستخدام ميزات الحالة ودورة الحياة بدون فئات.قبل ذلك، كان عليك كتابة مكونات الفئة للاحتفاظ بالحالة المحلية، أو الاشتراك في البيانات الخارجية، أو التفاعل مع أحداث دورة الحياة مثل التحميل والإلغاء.

تكمن المشكلة الكبيرة في الفئات في أن المنطق ذي الصلة غالبًا ما يكون مقسمًا عبر طرق متعددة لدورة الحياة مثل componentDidMount, componentDidUpdate و componentWillUnmountسينتهي بك الأمر بأجزاء من نفس الميزة موزعة على طرق مختلفة بناءً على متى يركضون بدلاً من ماذا نعم، وهذا ما يجعل قراءة الكود واختباره وإعادة استخدامه أكثر صعوبة.

تقوم الخطافات بقلب هذا النموذج.: مع useState يمكنك ربط الحالة مباشرةً بمكون الدالة، ومع useEffect يمكنك ربط الآثار الجانبية مباشرةً بالمنطق الذي يحتاجها. بهذه الطريقة، يمكنك تجميع كل ما يتعلق بمشكلة واحدة في مكان واحد، واستخراج الخطافات القابلة لإعادة الاستخدام بسهولة لاحقًا.

من بين جميع الخطافات، useState و useEffect هي العناصر الأساسيةيمكنك بناء معظم الميزات اليومية باستخدام هذين العنصرين فقط: حالة واجهة المستخدم مثل النماذج والمفاتيح، وطلبات الشبكة، والاشتراكات، والمؤقتات، وتحديثات DOM، والمزيد. (خطافات أخرى)useRef, useReducer, useContext, useMemo...) رائعة، لكنها مبنية على نفس الأفكار.

قواعد خطافات React التي يجب ألا تخالفها أبدًا

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

القاعدة الأولى: استدعاء الخطافات فقط داخل مكونات وظائف React أو الخطافات المخصصةلا يمكنك استخدام useState or useEffect في مكونات الفئة، أو دوال الأدوات المساعدة العادية، أو خارج أي مكون. نمط كهذا غير صالح:

import React, { Component, useState } from 'react';

class App extends Component {
  // ❌ This will throw - hooks don’t work in classes
  const  = useState(0);
  render() {
    return <h1>Hello, I am a Class Component!</h1>;
  }
}

النهج الصحيح هو الانتقال إلى مكون دالة إذا كنت ترغب في استخدام الخطافات:

import React, { useState } from 'react';

function App() {
  const  = useState('');

  return (
    <div>
      Your JSX code goes in here...
    </div>
  );
}

export default App;

القاعدة الثانية: استدعِ الخطافات فقط على المستوى الأعلى من مكونكهذا يعني عدم وجود خطافات داخل الحلقات أو الشروط أو الدوال المتداخلة. يعتمد React على استدعاء الخطافات بنفس الترتيب في كل عملية عرض لضمان "التوافق" بين كل عنصر وآخر. useState و useEffect هذا الاستدعاء مع بياناته المخزنة غير صالح:

function BadComponent({ enabled }) {
  if (enabled) {
    // ❌ Wrong: hook inside a conditional
    const  = useState(0);
  }
  // ...
}

بدلاً من ذلك، قم بتعريف الخطافات بشكل غير مشروط في الأعلى واستخدم الشروط داخل التأثير أو JSXيجب استدعاء الخطاف دائمًا، ولكن يمكن أن يكون المنطق الذي يقوم بتشغيله مشروطًا:

function ConditionalEffectComponent() {
  const  = useState(false);

  useEffect(() => {
    if (isMounted) {
      console.log('Component mounted');
    }
  }, );

  return (
    <div>
      <button onClick={() => setIsMounted(!isMounted)}>
        {isMounted ? 'Unmount' : 'Mount'}
      </button>
    </div>
  );
}

القاعدة الضمنية الثالثة هي أنه يجب استيراد الخطافات من React (أو مكتبة خطافات)، وليس تنفيذها بشكل مخصص.هذا واضح، لكن يجدر ذكره: يكمن السحر في موزع الخطافات الداخلي لـ React الذي يتتبع استدعاءات الخطافات عبر عمليات العرض.

إدارة الحالة المحلية بشكل صحيح باستخدام useState

useState يتيح لك هذا إرفاق حالة بمكون دالة واستلام كل من القيمة الحالية ودالة التحديثمن الناحية المفاهيمية، هو النظير الوظيفي لـ this.state و this.setState في مكونات الفئة.

مثال مضاد بسيط مع useState يبدو مثل هذا:

import React, { useState } from 'react';

function Counter() {
  const  = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default Counter;

عندما تتصل useState(initialValue)يقوم React بتخزين تلك الحالة وإرجاع زوج منالقيمة الحالية للحالة ودالة التعيين. على عكس المتغيرات المحلية العادية، تبقى الحالة محفوظةً عبر عمليات العرض، لذا فإن count لا تتم إعادة تعيين القيمة إلى 0 في كل مرة يتم فيها تشغيل وظيفة المكون.

يمكنك استخدام أي قيمة قابلة للتسلسل للحالة: الأرقام، والسلاسل النصية، والقيم المنطقية، والمصفوفات، والكائنات، وحتى الدوال..يمكنك أيضًا الاتصال useState يتم تكرار ذلك عدة مرات في نفس المكون للحفاظ على القيم ذات الصلة منفصلة بدلاً من وضع كل شيء في كائن واحد.

عندما تعتمد قيمة الحالة الجديدة على القيمة السابقة، استخدم دائمًا نموذج التحديث الوظيفي.وهذا يمنع حدوث الأخطاء عند حدوث تحديثات متعددة للحالة في تتابع سريع:

setCount(prev => prev + 1);

ومن التفاصيل الدقيقة الأخرى، ولكنها مهمة، أن استدعاء دالة التعيين يستبدل قيمة الحالة بأكملها، ولا يدمج الكائنات مثل this.setState في الفصول الدراسيةإذا كانت حالتك عبارة عن كائن أو مصفوفة، فأنت بحاجة إلى توزيع القيمة السابقة بنفسك:

const  = useState({ name: 'Alex', age: 30 });

// ✅ Correct: copy and update
setUser(prev => ({ ...prev, age: prev.age + 1 }));

بالنسبة للقيم الأولية المكلفة، يمكنك تهيئة الحالة بشكل كسول عن طريق تمرير دالة إلى useStateسيقوم React باستدعائه فقط عند أول عملية عرض:

const  = useState(() => calculateInitialValue());

معالجة الآثار الجانبية باستخدام useEffect

useEffect هي واجهة برمجة تطبيقات React لتشغيل التأثيرات الجانبية في مكونات الدوال. "الأثر الجانبي" هو أي شيء يلامس العالم الخارجي: جلب البيانات، والتسجيل، والتغييرات المباشرة في DOM، والاشتراكات، والمؤقتات، وواجهات برمجة تطبيقات المتصفح، وما إلى ذلك.

من الناحية النظرية، useEffect يستبدل مجموعة من componentDidMount, componentDidUpdate و componentWillUnmount مكونات الفئةبدلاً من تقسيم تأثير واحد عبر ثلاث طرق لدورة الحياة، يمكنك تعريفه مرة واحدة والسماح لـ React بالتعامل مع وقت تشغيله ووقت تنظيفه.

التوقيع الأساسي هو useEffect(setup, dependencies?). setup الدالة هي جسم التأثير الخاص بك؛ ويمكنها اختيارياً إرجاع دالة تنظيف. dependencies تُخبر المصفوفة React متى يجب إعادة تشغيل التأثير.

useEffect(() => {
  // side effect logic here

  return () => {
    // optional cleanup logic here
  };
}, );

بشكل افتراضي، وبدون الوسيط الثاني، سيتم تشغيل التأثير بعد كل عملية عرض. (عملية التثبيت الأولى وكل تحديث لاحق). غالبًا ما يكون ذلك كثيرًا جدًا بالنسبة لطلبات الشبكة أو العمليات الحسابية المعقدة.

من الأنماط الشائعة جدًا تحديث شيء خارجي كلما تغير جزء من الحالةعلى سبيل المثال، تحديث عنوان الصفحة بناءً على عدد النقرات:

import React, { useState, useEffect } from 'react';

function Counter() {
  const  = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, ); // effect re-runs only when `count` changes

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

تُعد مصفوفة التبعية أمراً بالغ الأهمية للأداء والصحة.يتحكم هذا الخيار في وقت إعادة تشغيل React للتأثير: إذا تغيرت أي تبعية وفقًا لـ Object.is بالمقارنة، يتم تنظيف التأثير وتشغيله مرة أخرى؛ إذا لم يتغير شيء، يتم تخطيه.

فهم مصفوفة التبعيات كالمحترفين

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

هناك ثلاثة تكوينات رئيسية للتبعية ستستخدمها طوال الوقت:

  • لا يوجد حجة ثانية: يتم تشغيل التأثير بعد كل عملية عرض.
  • مصفوفة فارغة []: يتم تشغيل التأثير مرة واحدة فقط عند التثبيت ويتم تنظيفه عند فك التثبيت.
  • مصفوفة تحتوي على قيم : يتم تشغيل التأثير بعد التثبيت وكلما تغيرت أي تبعية.

عندما تكون التبعيات عبارة عن قيم أولية (أرقام، سلاسل نصية، قيم منطقية)، يكون هذا الأمر واضحًا ومباشرًا.تبدأ المشاكل عند وضع الكائنات أو المصفوفات أو الدوال داخل التبعيات، لأن المساواة تعتمد على المراجع. يُعتبر كائنان متطابقان بمرجعين مختلفين "مختلفين"، مما يتسبب في إعادة تشغيل البرنامج في كل عملية عرض.

لنفترض وجود تأثير يعتمد على team كائن من الخصائص:

function Team({ team }) {
  useEffect(() => {
    console.log(team.id, team.active);
  }, ); // ⚠️ might re-run every render if `team` reference changes
}

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

النسخة الأكثر أمانًا لا تتعقب إلا ما يحتاجه التأثير فعلاً:

function Team({ team }) {
  const { id, active } = team;

  useEffect(() => {
    console.log(id, active);
  }, );
}

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

function Team({ team }) {
  const { id, active, name } = team;

  useEffect(() => {
    const localTeam = { id, active, name };
    // use `localTeam` here
  }, );
}

كحل أخير، يمكنك استخدام التخزين المؤقت مع useMemo or useCallback للأشياء أو الوظائف باهظة الثمنلكن تذكر أن التخزين المؤقت نفسه له تكلفة. لا تستخدمه في كل مكان "احتياطاً"؛ أضفه فقط عندما تتسبب تبعية معينة في مشاكل حقيقية في الأداء.

تنظيف الآثار بشكل صحيح

بعض الآثار الجانبية تخصص موارد يجب تحريرها: الاشتراكات، والمآخذ، والفترات الزمنية، والمهلات، ومستمعي الأحداث، وما إلى ذلك. إن نسيان تنظيفها يمكن أن يؤدي بسهولة إلى تسرب الذاكرة أو تكرار العمل.

In useEffectتتم معالجة عملية التنظيف عن طريق إرجاع دالة من التأثيرسيقوم React باستدعاء هذه الوظيفة قبل تشغيل التأثير مرة أخرى مع التبعيات الجديدة، وأيضًا لمرة أخيرة عند إلغاء تحميل المكون.

import { useEffect } from 'react';

function LogMessage({ message }) {
  useEffect(() => {
    const log = setInterval(() => {
      console.log(message);
    }, 1000);

    return () => {
      clearInterval(log);
    };
  }, );

  return <div>logging to console "{message}"</div>;
}

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

يُعد هذا الاقتران بين "الإعداد والتنظيف" أساسيًا للنموذج الذهني لـ useEffectحاول أن تنظر إلى كل تأثير كعملية مستقلة تبدأ في دالة الإعداد وتنتهي تمامًا في دالة التنظيف. قد يُشغّل React عدة دورات إعداد/تنظيف أثناء التطوير (خاصةً في الوضع الصارم) لاختبار مدى فعالية عملية التنظيف في إلغاء كل شيء.

ومن الأمثلة الكلاسيكية على ذلك الاشتراك في مصدر خارجي، مثل واجهة برمجة تطبيقات الدردشة أو حدث المتصفح (انظر معالجة حدث onKeyDown في React):

useEffect(() => {
  function handleClick(event) {
    console.log('Clicked', event.clientX, event.clientY);
  }

  document.addEventListener('click', handleClick);

  return () => {
    document.removeEventListener('click', handleClick);
  };
}, []); // runs once on mount, cleans up on unmount

استخدام useState و useEffect معًا لجلب البيانات

إحدى أكثر التركيبات شيوعًا في العالم الحقيقي هي استخدام useState و useEffect لجلب البيانات من واجهة برمجة التطبيقات (API). أنت تحتفظ بالبيانات (وربما علامات التحميل/الخطأ) في الحالة، وتقوم بتنفيذ الطلب في تأثير يتم تشغيله عند تحميل المكون أو عند تغيير بعض المعلمات.

يبدو النمط الأساسي لجلب البيانات بمجرد تحميلها كالتالي::

import { useEffect, useState } from 'react';

function FetchItems() {
  const  = useState([]);

  useEffect(() => {
    let ignore = false;

    async function fetchItems() {
      try {
        const response = await fetch('/items');
        const fetchedItems = await response.json();
        if (!ignore) {
          setItems(fetchedItems);
        }
      } catch (error) {
        console.error('Error fetching items:', error);
      }
    }

    fetchItems();

    return () => {
      // avoid updating state if the component unmounted
      ignore = true;
    };
  }, []);

  return (
    <div>
      {items.map(item => (
        <div key={item.id ?? item}>{item.name ?? item}</div>
      ))}
    </div>
  );
}

هنا، تضمن مصفوفة التبعية الفارغة أن يتم تنفيذ الطلب مرة واحدة فقط.. داخلي ignore تُعدّ العلامة طريقة بسيطة لتجنب تعيين الحالة على مكون غير مُحمّل في حالة تأخر استجابة الطلب.

من الشائع أيضاً إضافة علامة تحميل وعرض مؤشر تحميل أو عنصر نائب أثناء تحميل البيانات.:

const Statistics = () => {
  const  = useState([]);
  const  = useState(true);

  useEffect(() => {
    const getStats = async () => {
      try {
        const statsData = await getData();
        setStats(statsData);
      } finally {
        setLoading(false);
      }
    };

    getStats();
  }, []);

  if (loading) {
    return <div>Loading statistics...</div>;
  }

  return (
    <ul>
      {stats.map(stat => (
        <li key={stat.id}>{stat.label}: {stat.value}</li>
      ))}
    </ul>
  );
};

إذا كان استعلامك يعتمد على مُعامل (مثل مُعامل الفئة أو عامل التصفية أو مُعامل المسار)، فأضف هذا المُعامل إلى مصفوفة التبعية. لذا، يتكرر التأثير عند حدوث التغيير:

useEffect(() => {
  async function fetchItems() {
    const response = await fetch(`/items?category=${category}`);
    const data = await response.json();
    setItems(data);
  }

  fetchItems();
}, );

التفكير من منظور "التأثيرات على كل عملية عرض" مقابل "دورات الحياة"

إذا كنت معتادًا على مكونات الفئات، فقد يكون من المغري رسم خريطة ذهنية لها useEffect طرق التركيب/التحديث/إلغاء التركيبلكن هذا عادةً ما يؤدي إلى مزيد من الارتباك. النموذج الذهني الأبسط هو: "تُنفَّذ التأثيرات بعد عمليات العرض، وقد تُزال قبل التشغيل التالي".

في الفصول الدراسية، كان عليك في كثير من الأحيان تكرار المنطق بين componentDidMount و componentDidUpdate لأنك أردتَ أن يعمل التأثير نفسه عند تحميل الصفحة وعند تحديثها. باستخدام الخطافات، يختفي هذا التكرار: تأثير واحد يغطي الحالتين، ويتولى React مهمة التنظيف بين عمليات التشغيل.

كما أن هذا التصميم يقضي على فئة كاملة من الأخطاء المتعلقة بعدم معالجة التحديثات بشكل صحيحعلى سبيل المثال، في مكون فئة يشترك في حالة اتصال صديق، من السهل نسيان إعادة الاشتراك عندما props.friend التغييرات، مما يتسبب في اشتراكات قديمة أو أعطال عند إلغاء التحميل. مع useEffect تلك القوائم friend.id باعتبارها تبعية، ستقوم React تلقائيًا بتشغيل عملية التنظيف للصديق القديم وإعداد الصديق الجديد.

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

تحسين سلوك تطبيق useEffect واستكشاف أخطائه وإصلاحها

عندما يتم تشغيل تأثير ما بشكل متكرر أكثر مما تتوقع، فإن أول شيء يجب التحقق منه هو مصفوفة التبعية.إما أن هناك تغييرًا في التبعية في كل عملية عرض (وهذا شائع مع الكائنات/الدوال المضمنة) أو أنك نسيت تحديد المصفوفة على الإطلاق.

يُعد تسجيل قيم التبعية طريقة سريعة لتصحيح الأخطاء:

useEffect(() => {
  console.log('Effect deps:', dep1, dep2);
}, );

إذا رأيت سجلات مختلفة في كل مرة، فتحقق من التبعية التي تتغير بالفعل.غالباً ما ستجد كائناً مضمناً أو دالة سهمية يُعاد إنشاؤها في كل عملية عرض. يمكن نقل عملية إنشاء الكائن داخل التأثير، أو رفع الدوال خارج المكون، أو تخزينها مؤقتاً باستخدام useCallback يمكنه تثبيت التبعيات عند الحاجة.

تحدث الحلقات اللانهائية عندما يعتمد تأثير ما على قيمة معينة ويقوم بتحديث تلك القيمة نفسها بشكل غير مشروط.. على سبيل المثال:

useEffect(() => {
  setCount(count + 1); // ⚠️ will cause a loop if `count` is a dependency
}, );

في كل مرة count التغييرات، وتأثيرها، والتحديثات count مرة أخرى، يؤدي ذلك إلى عملية عرض أخرى، وهكذا دواليك.لكسر هذا النمط، فكر فيما إذا كان تحديث الحالة ينتمي حقًا إلى تأثير، أو ما إذا كان ينبغي تشغيله عن طريق تفاعل المستخدم بدلاً من ذلك، أو ما إذا كان بإمكانك الاعتماد على قيمة مختلفة.

أحيانًا ترغب في قراءة أحدث قيمة لحالة أو خاصية معينة داخل تأثير ما دون أن تؤدي تلك القيمة إلى إعادة تشغيل التأثير.في تلك السيناريوهات المتقدمة، تُستخدم واجهات برمجة التطبيقات الأحدث مثل "تأثير الأحداث" (عبر useEffectEvent يمكن أن تساعد المراجع (في وثائق React) أو المراجع، ولكن بالنسبة لمعظم الحالات العملية، فإن البقاء وفياً للاعتمادات هو أكثر أماناً وبساطة.

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

المادة ذات الصلة:
تم الحل: كيفية تثبيت خطافات التفاعل الأصلية باستخدام
الوظائف ذات الصلة: