React: قواعد استخدام الأخطاف والمشاكل الشائعة

آخر تحديث: 2026-08-26

1. ما ستتعلمه



2. قصة كابوس في تصحيح الأخطاء

(1) المشكلة: استغرق تصحيح خطأ واحد في useEffect 3 ساعات

كتبت أليس مكونًا للعد التنازلي:

▶ مثال: عرض توضيحي لـ «Hooks Rule»

JSX
function Countdown({ targetDate }) {
  const [timeLeft, setTimeLeft] = useState(computeTimeLeft())

  function computeTimeLeft() {
    return Math.max(0, Math.floor((new Date(targetDate) - Date.now()) / 1000))
  }

  useEffect(() => {
    const timer = setInterval(() => {
      // ❌ Closure Pitfalls:timeLeft Always the initial value
      setTimeLeft(timeLeft - 1)
    }, 1000)

    return () => clearInterval(timer)
  }, [])  // I want it to start only when mounted

  return <p>Remaining:{timeLeft}s</p>
}
▶ جرّب الكود

خطأ: يتوقف العد التنازلي دائمًا عند القيمة الأولية -1. ويرجع ذلك إلى أن القيمة timeLeft التي يتم التقاطها في دالة الاستدعاء setInterval هي القيمة الناتجة عن عملية العرض الأولى (فخ الإغلاق).

(2) تصحيح

JSX
// ✅ Fix 1:Functional Updates
useEffect(() => {
  const timer = setInterval(() => {
    setTimeLeft(prev => Math.max(0, prev - 1))  // prev Always the latest value
  }, 1000)
  return () => clearInterval(timer)
}, [])

// ✅ Fix 2:Or put targetDate Add to the dependencies array
useEffect(() => {
  const timer = setInterval(() => {
    setTimeLeft(prev => Math.max(0, prev - 1))
  }, 1000)
  return () => clearInterval(timer)
}, [targetDate])  // targetDate Restart the timer when a change occurs
▶ جرّب الكود

الدرس: لا تقم أبدًا بقراءة الحالة مباشرةً في useEffect لتحديث نفسك. استخدم التحديثات الوظيفية setState(prev => prev + 1).



3. القاعدتان الثابتتان في صياغة العناوين الجذابة

(1) القاعدة 1: لا تُستدعى الدوال المعلقة إلا في المستوى الأعلى

لا تستدعِ الدوال «hooks» داخل الحلقات أو العبارات الشرطية أو الدوال المتداخلة:

JSX
function BadComponent({ showExtra }) {
  const [name, setName] = useState('')  // ✅ Top Floor

  // ❌ Rule Violation:Called within a condition Hook
  if (showExtra) {
    const [extra, setExtra] = useState('')  // Error!
    useEffect(() => { /* ... */ }, [])
  }

  // ❌ Rule Violation:Called within a loop Hook
  for (let i = 0; i < items.length; i++) {
    useEffect(() => { /* ... */ }, [])  // Error!
  }

  // ✅ Correct:Conditional logic in Hook Internal Processing
  const [extra, setExtra] = useState('')
  useEffect(() => {
    if (showExtra) { /* Conditional logic is placed in useEffect Inside */ }
  }, [showExtra])
}
▶ جرّب الكود
موقع المكالمة صالحة السبب
مكونات الوظائف ذات المستوى الأعلى يظل ترتيب سلسلة «الهوكات» ثابتًا في كل عملية عرض
الخطافات المخصصة من المستوى الأعلى الخطافات المخصصة هي في الأساس سلاسل استدعاءات فرعية لمكونات الدالة
داخل if / for / while تؤدي التغييرات في الشروط إلى تباين في عدد مرات استدعاء الـ«هوك»، مما يؤدي إلى اختلال محاذاة القائمة المرتبطة
وظائف المساعدة العامة غير موجودة في سياق مكون React؛ لا توجد سلسلة Hooks
طرق مكونات الفئة لا تحتوي مكونات الفئة على آلية تسلسل «Hooks»
داخل دالة الاستدعاء المرتدة لـ useEffect دالة الاستدعاء المرتدة ليست في المستوى الأعلى للمكون، لذا فإن ترتيب الاستدعاء غير متوقع

(2) القاعدة 2: لا تُستدعى الخطافات إلا في مكونات الدالة أو الخطافات المخصصة

JSX
// ❌ Called from a regular function Hook
function getData() {
  const [data, setData] = useState(null)  // Error!
}

// ❌ Calling within a class component Hook
class MyClass extends React.Component {
  render() {
    const [count, setCount] = useState(0)  // Error!
  }
}

// ✅ Custom Hook Called in
function useCustomHook() {
  const [data, setData] = useState(null)  // ✅ Correct
}
▶ جرّب الكود

(3) لماذا هاتان القاعدتان مهمتان جدًّا؟

100%
graph TB
    A[Component Rendering] --> B[Hook Linked List]
    B --> C[Hook 1: useState<br/>Linked List Nodes 1]
    B --> D[Hook 2: useEffect<br/>Linked List Nodes 2]
    B --> E[Hook 3: useRef<br/>Linked List Nodes 3]
    D -->|Conditional rendering causes skipping| F[Linked List Misalignment!]
    F --> G[Hook 2 What I read was Hook 3 data]
    G --> H[Bug! Data Chaos]
    
    style F fill:#ff4d4f,color:#fff
    style H fill:#ff4d4f,color:#fff

يعتمد React على ترتيب استدعاء الـ Hooks لمزامنة الحالة. وإذا اختلف ترتيب الاستدعاءات في كل عملية عرض، فإن الحالة تصبح غير متزامنة.



4. المأزق الأول: مأزق الإغلاق المتقادم

أكثر الأخطاء شيوعًا في Hooks.

JSX
function Counter() {
  const [count, setCount] = useState(0)

  // ❌ Closure Pitfalls
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count)  // Always 0!
      setCount(count + 1)  // Set permanently to 1
    }, 1000)
    return () => clearInterval(timer)
  }, [])  // count is "locked" to first render

  // ✅ Fix 1:Functional Updates
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prev => prev + 1)  // prev Always the latest value
    }, 1000)
    return () => clearInterval(timer)
  }, [])

  // ✅ Fix 2:Add Dependencies
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1)
    }, 1000)
    return () => clearInterval(timer)
  }, [count])  // count Restart the timer when a change occurs
}
▶ جرّب الكود

عند حدوث ذلك: تمت قراءة قيمة لم يتم الإعلان عنها في مصفوفة التبعيات في useEffect أو useCallback أو useMemo.

سيناريوهات مشاكل الإغلاق الأعراض الحلول
قراءة الحالة في setInterval القيمة في المؤقت لا تتغير أبدًا تحديثات وظيفية setState(prev => ...)
قراءة الحالة في useCallback استخدام قيمة قديمة في دالة الاستدعاء إضافة الحالة إلى مصفوفة التبعيات أو استخدام مرجع
تأخر تنفيذ معالج الحدث القيمة state في استدعاء الرد غير المتزامن قديمة استخدم useRef لتخزين أحدث قيمة
تبعيات useEffect مفقودة المتغيرات المستخدمة في التأثير لم يتم تحديثها أكمل مصفوفة التبعيات
حدوث حالة تنافس بعد طلب غير متزامن الطلب القديم يحل محل النتائج الجديدة أثناء التبديل السريع استخدم العلامة ignore أو AbortController

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



5. المأزق الثاني: حلقة لا نهائية في useEffect

(1) السبب الأول: عدم وجود مصفوفة من التبعيات

JSX
// ❌ Infinite Loop!
useEffect(() => {
  fetch('/api/data').then(setData)
})  // No array dependencies → Executed on every render → setData Trigger Rendering → Infinite Loop

// ✅ Empty array of dependencies
useEffect(() => {
  fetch('/api/data').then(setData)
}, [])
▶ جرّب الكود

(2) السبب الثاني: أنواع المرجعية في مصفوفة التبعية

JSX
function App() {
  const [count, setCount] = useState(0)

  // ❌ Infinite Loop!
  useEffect(() => {
    console.log('useEffect Execute')
  }, [{ count }])  // Create a new object on every render {} → Differences in Citation → Infinite Execution

  // ✅ Correct:Using Primitive Types
  useEffect(() => {
    console.log('useEffect Execute')
  }, [count])  // Basic Types,If the value remains unchanged, the action is not performed.
}
▶ جرّب الكود

(3) السبب الثالث: تعديل التبعيات داخل useEffect

JSX
const [items, setItems] = useState([1, 2, 3])

// ❌ Infinite Loop!
useEffect(() => {
  setItems([...items, 4])  // Edit items → Render Again → useEffect Execute again...
}, [items])

// ✅ Correct:Why is this change necessary? items?Generally, it is not necessary.
// If it's initialization,use  useRef Flag indicating whether initialization has been completed
const initialized = useRef(false)
useEffect(() => {
  if (!initialized.current) {
    setItems([...items, 4])
    initialized.current = true
  }
}, [items])
▶ جرّب الكود

(4) السبب الرابع: الدوال باعتبارها تبعيات

أسباب الحلقات اللانهائية الكود الخاطئ الكود الصحيح
لا توجد تبعيات للمصفوفات useEffect(fn) useEffect(fn, [])
أنواع المرجع useEffect(fn, [{ count }]) useEffect(fn, [count])
تعديل التبعيات في effect useEffect(() => setItems(...), [items]) وضع علامة على عملية التهيئة باستخدام useRef
الوظائف كمتطلبات useEffect(fn, [handleClick]) useCallback المراجع المستقرة
الكائن كعنصر تابع useEffect(fn, [config]) استخراج قيمة من نوع أساسي أو استخدام useMemo
JSX
// ❌ Each render creates a new function reference
function App() {
  const handleClick = () => { /* ... */ }

  useEffect(() => {
    fetch('/api').then(setData)
  }, [handleClick])  // handleClick A new reference every time → Infinite Execution

  // ✅ use  useCallback Stable Citation
  const handleClick = useCallback(() => { /* ... */ }, [])

  useEffect(() => {
    fetch('/api').then(setData)
  }, [handleClick])  // handleClick Citation unchanged,Will not be executed again
}
▶ جرّب الكود

6. المأزق الثالث: التحديثات غير المتزامنة مع useState

JSX
function Counter() {
  const [count, setCount] = useState(0)

  function handleClick() {
    // You thought it would +3,The result was only +1
    setCount(count + 1)
    setCount(count + 1)
    setCount(count + 1)  // All three times count + 1, but count = 0
    console.log(count)   // Output 0,No 3
  }

  // ✅ Functional Updates
  function handleCorrectClick() {
    setCount(prev => prev + 1)  // prev = 0 → 1
    setCount(prev => prev + 1)  // prev = 1 → 2
    setCount(prev => prev + 1)  // prev = 2 → 3
    // count Or 0(The rendered values for this pass),But the next time it's rendered, it will be 3
  }
}
▶ جرّب الكود

المفهوم الأساسي: تتم تحديثات useState بشكل غير متزامن ومعالجتها دفعة واحدة. خلال دورة معالجة حدث واحد، يتم دمج عدة مثيلات من setCount في عملية عرض واحدة. تُستخدم التحديثات الوظيفية لضمان أن كل تحديث يستند إلى أحدث القيم.



7. المأزق الرابع: استخدام async في useEffect

JSX
// ❌ useEffect Cannot be transferred directly async Function
useEffect(async () => {
  const data = await fetch('/api/data')
  setData(await data.json())
}, [])
// Error!async returns Promise, useEffectt Expected return value of the cleanup function(or  undefined)

// ✅ Correct:Defined internally async Function
useEffect(() => {
  async function fetchData() {
    const res = await fetch('/api/data')
    const data = await res.json()
    setData(data)
  }
  fetchData()  // Internal Call async Function
}, [])
▶ جرّب الكود

8. مثال كامل: 10 جداول شائعة لتشخيص الأخطاء

JSX
// ============================================
// Example:10 A common Hooks Bug + Remediation Plan
// ============================================

// Bug 1:Conditions Hook
function Bug1({ show }) {
  // if (show) const [x] = useState(0)  // ❌
  const [x] = useState(0)  // ✅ Move to the top
}

// Bug 2:Closure Pitfalls
function Bug2() {
  const [count, setCount] = useState(0)
  useEffect(() => {
    const id = setInterval(() => {
      // setCount(count + 1)  // ❌ Closure
      setCount(prev => prev + 1)  // ✅
    }, 1000)
    return () => clearInterval(id)
  }, [])
}

// Bug 3:Infinite Loop(Dependency on Reference Types)
function Bug3() {
  const [data, setData] = useState(null)
  useEffect(() => {
    fetch('/api').then(res => res.json()).then(setData)
  }, [])  // ✅ Empty array
  // }, [data])  // ❌ data The change triggered another one useEffect
}

// Bug 4:Forgot to clean up
function Bug4() {
  useEffect(() => {
    const id = setInterval(() => console.log('tick'), 1000)
    return () => clearInterval(id)  // ✅ Must be cleaned up
  }, [])
}

// Bug 5:Edit directly state
function Bug5() {
  const [user, setUser] = useState({ name: 'Alice', age: 28 })
  function updateAge() {
    // user.age = 29  // ❌ Edit directly
    setUser({ ...user, age: 29 })  // ✅ Create a New Object
  }
}

// Bug 6:Not needed useMemo
function Bug6({ count }) {
  // const doubled = useMemo(() => count * 2, [count])  // ❌ Unnecessary
  const doubled = count * 2  // ✅ Simple calculations are not required useMemo
}

// Bug 7:effect Read from props But do not declare dependencies
function Bug7({ userId }) {
  const [user, setUser] = useState(null)
  useEffect(() => {
    fetch('/api/users/' + userId).then(res => res.json()).then(setUser)
  }, [userId])  // ✅ userId Dependencies must be added
  // }, [])  // ❌ userId Changes will not trigger a new request
}

// Bug 8:useMemo Return Function
function Bug8() {
  // const fn = useMemo(() => { return () => console.log('hi') }, [])  // ❌ Should use useCallback
  const fn = useCallback(() => console.log('hi'), [])  // ✅
}

// Bug 9:in  render Side effects caused by the drug
function Bug9() {
  const [data, setData] = useState(null)
  // fetch('/api/data').then(setData)  // ❌ A request is made for every render
  useEffect(() => { fetch('/api/data').then(res => res.json()).then(setData) }, [])  // ✅
}

// Bug 10:State dependency not fully declared
function Bug10({ items }) {
  const [filter, setFilter] = useState('')
  // const filtered = items.filter(i => i.includes(filter))  // ✅ in  render Calculate directly in the middle
  const filtered = useMemo(() => items.filter(i => i.includes(filter)), [items, filter])
  // useMemo The correct dependencies must be in place.
}


9. نصائح حول تصحيح الأخطاء

(1) أداة تحليل الأداء في React DevTools

JSX
// in  React DevTools 's  Profiler In the tab:
// 1. Click the Record button
// 2. Triggering Actions in the App
// 3. Stop Recording
// 4. View the flame diagram:Each column is a component,Rendering Time by Height
// I see components that shouldn't be re-rendered being rendered → Inspection useCallback / React.memo
▶ جرّب الكود

(2) لماذا قمت بالتجسيد

BASH
npm install @welldone-software/why-did-you-render
JSX
// src/wdyr.js
import React from 'react'

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render')
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  })
}
// The console will print:Why did this component re-render??Because which one Props It's changed?
▶ جرّب الكود

(3) تصحيح الأخطاء باستخدام useWhyDidYouUpdate

JSX
function useWhyDidYouUpdate(name, props) {
  const previousProps = useRef(props)

  useEffect(() => {
    const allKeys = Object.keys({ ...previousProps.current, ...props })
    const changes = {}

    allKeys.forEach(key => {
      if (previousProps.current[key] !== props[key]) {
        changes[key] = {
          from: previousProps.current[key],
          to: props[key]
        }
      }
    })

    if (Object.keys(changes).length > 0) {
      console.log(`[why-did-you-update] ${name}:`, changes)
    }

    previousProps.current = props
  })
}

// Usage
function MyComponent(props) {
  useWhyDidYouUpdate('MyComponent', props)
  return <div>{props.name}</div>
}
▶ جرّب الكود

▶ المثال 2: استخدام «هوك» مخصص لتجنب مشاكل الإغلاق — useStableCallback

JSX
// ============================================
// Example:useStableCallback——The Ultimate Solution to Closure Pitfalls
// Features:A callback function that always returns a reference to a stable value,However, the latest logic is executed when the function is called.
// ============================================

import { useRef, useCallback, useState, useEffect } from 'react'

// ---- Custom Hook:A Steady Pullback ----
function useStableCallback(callback) {
  // use  ref Store the latest callback
  const callbackRef = useRef(callback)

  // Update after each render ref Callbacks in
  useEffect(() => {
    callbackRef.current = callback
  })

  // Return a function that never changes
  return useCallback((...args) => {
    // Execute the latest version of the callback when it is called
    return callbackRef.current(...args)
  }, [])
}

// ---- Usage useStableCallback Avoiding Closure Pitfalls ----
function TimerWithStableCallback() {
  const [count, setCount] = useState(0)
  const [isRunning, setIsRunning] = useState(false)

  // This callback function depends on count(Each render is a new function)
  function onTick() {
    console.log('Current Count:', count)
    // If you use it directly here count,This can lead to a closure trap
    // but useStableCallback ensures latest version is used.
  }

  // ✅ use  useStableCallback Package,Citations are always reliable
  const stableOnTick = useStableCallback(onTick)

  useEffect(() => {
    if (!isRunning) return

    const timer = setInterval(() => {
      // stableOnTick The reference will not change,However, the latest version is executed each time it is called. onTick
      stableOnTick()
      setCount(prev => prev + 1)
    }, 1000)

    return () => clearInterval(timer)
  }, [isRunning, stableOnTick])  // stableOnTick Forever Unchanging,It will not cause an infinite loop

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto', textAlign: 'center' }}>
      <h2>⏱️ Solutions to Closure Pitfalls</h2>
      <p style={{ fontSize: '48px', fontWeight: 'bold', margin: '20px 0' }}>{count}</p>
      <button
        onClick={() => setIsRunning(!isRunning)}
        style={{
          padding: '10px 24px',
          backgroundColor: isRunning ? '#ff4d4f' : '#52c41a',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer',
          fontSize: '16px'
        }}
      >
        {isRunning ? '⏸ Pause' : '▶ Start'}
      </button>
      <p style={{ marginTop: '12px', color: '#666', fontSize: '13px' }}>
        Open the console to view the logs——useStableCallback Ensure that the timer does not get stuck due to closures
      </p>
    </div>
  )
}

// ---- Comparison:Do not use useStableCallback version ----
function TimerBroken() {
  const [count, setCount] = useState(0)
  const [isRunning, setIsRunning] = useState(false)

  useEffect(() => {
    if (!isRunning) return
    // ❌ Use as is setInterval,count Locked in a closure
    const timer = setInterval(() => {
      setCount(count + 1)  // Always 0 + 1 = 1
    }, 1000)
    return () => clearInterval(timer)
  }, [isRunning])  // Missing count Dependency

  // Remediation Plan:Update Using a Function
  // setCount(prev => prev + 1)  // ✅ Not necessary count Dependency

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto', textAlign: 'center', marginTop: '40px' }}>
      <h3>❌ The version with the issue(For comparison)</h3>
      <p style={{ fontSize: '48px', fontWeight: 'bold', margin: '20px 0', color: '#ff4d4f' }}>{count}</p>
      <button onClick={() => setIsRunning(!isRunning)}
        style={{ padding: '10px 24px', backgroundColor: '#d9d9d9', color: '#333', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
        Start(It gets stuck 1)
      </button>
    </div>
  )
}

▶ المثال 3: المشكلات الشائعة وحلولها في عمليات التحديث المجمعة باستخدام useState

JSX
function ShoppingCart() {
  const [items, setItems] = useState([])
  const [total, setTotal] = useState(0)

  function addItem(item) {
    setItems([...items, item])
    setTotal(items.reduce((sum, i) => sum + i.price, 0) + item.price)
  }

  function addItemFixed(item) {
    setItems(prev => [...prev, item])
    setTotal(prev => prev + item.price)
  }

  function addMultiple() {
    setItems(prev => [...prev, { id: 1, name: 'Book', price: 29 }])
    setItems(prev => [...prev, { id: 2, name: 'Pen', price: 5 }])
    setTotal(prev => prev + 34)
  }

  return (
    <div>
      <h3>Shopping Cart</h3>
      <ul>
        {items.map((item, idx) => (
          <li key={idx}>{item.name} - ${item.price}</li>
        ))}
      </ul>
      <p>Total: ${total}</p>
      <button onClick={() => addItemFixed({ id: 3, name: 'Notebook', price: 12 })}>
        Add Notebook
      </button>
      <button onClick={addMultiple}>Add Book + Pen</button>
    </div>
  )
}
▶ جرّب الكود

▶ المثال 4: حالات التنافس في الطلبات غير المتزامنة باستخدام useEffect

JSX
function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    let ignore = false
    setLoading(true)

    async function fetchUser() {
      try {
        const res = await fetch(`/api/users/${userId}`)
        const data = await res.json()
        if (!ignore) {
          setUser(data)
          setLoading(false)
        }
      } catch (err) {
        if (!ignore) setLoading(false)
      }
    }

    fetchUser()
    return () => { ignore = true }
  }, [userId])

  if (loading) return <p>Loading...</p>
  if (!user) return <p>No user found</p>
  return (
    <div>
      <h3>{user.name}</h3>
      <p>Email: {user.email}</p>
      <p>Role: {user.role}</p>
    </div>
  )
}
▶ جرّب الكود

▶ المثال 5: استخدام useReducer بدلاً من عدة استدعاءات لـ useState لتجنب عدم اتساق الحالة

JSX
const initialState = { count: 0, step: 1, history: [] }

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {
        ...state,
        count: state.count + state.step,
        history: [...state.history, `+${state.step}`],
      }
    case 'decrement':
      return {
        ...state,
        count: state.count - state.step,
        history: [...state.history, `-${state.step}`],
      }
    case 'setStep':
      return { ...state, step: action.payload }
    case 'reset':
      return initialState
    default:
      return state
  }
}

function CounterWithHistory() {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <div>
      <h3>Count: {state.count}</h3>
      <label>
        Step:
        <input
          type="number"
          value={state.step}
          onChange={e => dispatch({ type: 'setStep', payload: Number(e.target.value) })}
        />
      </label>
      <button onClick={() => dispatch({ type: 'increment' })}>+{state.step}</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-{state.step}</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
      <p>History: {state.history.join(', ')}</p>
    </div>
  )
}


❓ أسئلة شائعة

س لماذا لا يتم تحديث الصفحة أحيانًا بعد استدعاء setState؟
ج الأسباب المحتملة: ① قمت بتعديل الكائن state مباشرةً (دون إنشاء مرجع جديد)؛ ② لم تتغير قيمة الحالة (يستخدم React Object.is لمقارنة القيم؛ والقيم المتطابقة لا تؤدي إلى إعادة العرض)؛ ③ نسيتَ إعلان المتغير المحدث في مصفوفة التبعيات الخاصة بـ useEffect؛ ④ قرأتَ قيمة إغلاق قديمة في استدعاء مرتد غير متزامن.
س ما الفرق بين تعيين مصفوفة التبعيات على «فارغة» وتركها فارغة في useEffect؟
ج ترك مصفوفة التبعيات فارغة useEffect(fn) → يتم التنفيذ بعد كل عملية عرض (عند التحميل + عند كل تحديث)؛ مصفوفة التبعيات الفارغة useEffect(fn, []) → يتم التنفيذ مرة واحدة فقط أثناء التثبيت؛ التبعيات غير الفارغة useEffect(fn, [a, b]) → يتم التنفيذ أثناء التثبيت + يتم التنفيذ عند تغير a أو b. المصدر الأكثر شيوعًا للأخطاء: تحديد مصفوفة فارغة مع استخدام state أو props داخل التأثير (فخ الإغلاق).

📖 ملخص


📝 تمارين

  1. المسألة الأساسية (مستوى الصعوبة: ⭐): اعثر على الخطأ في الشفرة أدناه وقم بإصلاحه:

    JSX
    function Timer() {
      const [time, setTime] = useState(0)
      useEffect(() => {
        const id = setInterval(() => {
          setTime(time + 1)
        }, 1000)
        return () => clearInterval(id)
      }, [])
      return <p>{time}s</p>
    }
    
    ▶ جرّب الكود
  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): اعثر على الأخطاء في الكود أدناه وقم بإصلاحها (3 أخطاء على الأقل):

    JSX
    function SearchPage() {
      const [query, setQuery] = useState('')
      const [results, setResults] = useState([])
      useEffect(async () => {
        const data = await fetch('/api/search?q=' + query)
        setResults(data)
      }, [])
      return <div>
        <input value={query} onChange={e => setQuery(e.target.value)} />
        {results.map(r => <p key={r.id}>{r.name}</p>)}
      </div>
    }
    
    ▶ جرّب الكود
  3. التحدي (الصعوبة: ⭐⭐⭐): قم بتنفيذ ربط useStableCallback يقبل دالة استدعاء مرتد ويُرجع دائمًا دالة ثابتة من حيث المرجع، ولكنه ينفذ أحدث دالة استدعاء مرتد عند استدعائه (الحل النهائي لمشكلة «فخ الإغلاق»).

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%