قواعد الخطافات اثنتان: لا تستدعِ الخطافات إلا في المستوى الأعلى من مكوّنك، ولا تستدعها أبدًا داخل شرط أو حلقة أو دالة متداخلة أو بعد return مبكر؛ ولا تستدعها إلا من مكوّنات الدوال أو من الخطافات المخصصة. وهما موجودتان لأن React تميّز الخطافات بترتيب استدعائك لها، لذا يجب أن يكون ذلك الترتيب نفسه في كل عرض.
تخزن React قيم خطافات المكوّن في قائمة مرتبطة بذلك المكوّن. وفي كل عرض تمر على القائمة: يحصل أول استدعاء خطاف على الخانة الأولى، والاستدعاء الثاني على الخانة الثانية، وهكذا. لا توجد أسماء، بل مواضع فقط. المثال أدناه نسخة مصغرة من هذه الفكرة في بضعة أسطر من JavaScript العادية، وليس كود React نفسه، فيمكنك كسره بأمان.
يملأ العرض الأول ثلاث خانات: Ada وCountess و36. انقر "Render without nickname": يُتخطى الاستدعاء الشرطي، فيأخذ استدعاء age الخانة 1 ويقرأ Countess. تلاحظ React الحقيقية عدم التطابق في بعض الحالات وترمي خطأ، وفي حالات أخرى تسلّم القيمة الخاطئة إلى الخطاف الخاطئ، وهذا أسوأ. انقل استدعاء toyUseState('Countess') إلى ما قبل if فيعطي الزران العمر الصحيح.
القاعدتان في الكود
function Profile({ user }) {
// Good: always called, always in this order
const [tab, setTab] = useState('posts');
const theme = useContext(ThemeContext);
useEffect(() => {
document.title = user.name;
}, [user.name]);
// Bad: inside a condition, a loop or a nested function
if (user.isAdmin) {
const [log, setLog] = useState([]); // breaks the order
}
}
استدعاء الخطاف الذي يُبلغ دائمًا، وبالترتيب نفسه، لا بأس به أينما كان في الجسم، ما دام يأتي قبل أي return مبكر.
إصلاح خطاف يُستدعى بشرط
تبلّغ أداة الفحص عن هذا بالرسالة React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. والإصلاح هو نفسه دائمًا: استدعِ الخطاف في كل مرة، وضع الشرط داخله.
// Broken: the effect exists on some renders and not others
function Notifications({ user }) {
if (user) {
useEffect(() => {
console.log('subscribe for', user.name);
}, [user]);
}
return null;
}
يُعلن التأثير في كل عرض، والشرط if بداخله يقرر ما إذا كان سيفعل شيئًا. سجّل الدخول والخروج بضع مرات فتُظهر Console اشتراكًا مع كل دخول وإلغاء اشتراك مع كل خروج.
الخطافات بعد إرجاع مبكر
الإرجاع المبكر return شرط أيضًا. ينتج هذا الرسالة "Rendered more hooks than during the previous render" في أول مرة تصبح فيها loading قيمتها false، لأن ذلك العرض يبلغ خطافًا لم يستدعه العرض السابق أبدًا. وعندما يستدعي عرضٌ خطافات أقل من السابق، تكون الرسالة "Rendered fewer hooks than expected".
// Broken: useState is skipped while loading
function Comments({ loading, comments }) {
if (loading) return <p>Loading...</p>;
const [expanded, setExpanded] = useState(false);
return <List items={comments} expanded={expanded} />;
}
كل خطاف يقع قبل return، فيستدعي الفرعان الخطافات نفسها. انقر "Toggle loading"، ووسّع القائمة، ثم بدّل التحميل مرتين: تتذكر القائمة أنها كانت موسّعة، لأن الحالة لم تُتخطَّ أبدًا.
الخطافات في حلقة
لا يمكنك استدعاء useState مرة لكل عنصر في حلقة، لأن عدد العناصر يتغير. أعطِ كل عنصر مكوّنه الخاص واستدعِ الخطاف هناك؛ ولكل نسخة خاناتها الخاصة.
الرسالة "Invalid hook call"
تبدأ الرسالة بـ "Invalid hook call. Hooks can only be called inside of the body of a function component." ولها ثلاثة أسباب معتادة:
- يعمل الخطاف بينما لا تعرض React مكوّن دالة. في معالج حدث، أو داخل دالة تأثير، أو في مكوّن صنف، أو على مستوى الوحدة. انقله إلى جسم المكوّن أو إلى خطاف مخصص، واستدعِ الخطاف المخصص من المكوّن.
- إصدارا
reactوreact-domغير متطابقين. ثبّت الإصدار نفسه من الاثنين. - توجد نسختان من React. حزمة محلية مربوطة أو مكتبة تجمع React الخاصة بها تجلب نسخة ثانية، والخطافات من نسخة لا تعمل مع مُصيِّر النسخة الأخرى. شغّل
npm ls reactوتأكد أن إصدارًا واحدًا فقط يظهر.
npm ls react
تعمل المعاينة هنا مثل بناء الإنتاج الذي لا يطبع هذه الرسائل: الخطاف المستدعى من معالج حدث يفشل بخطأ TypeError عادي مثل "Cannot read properties of null"، وتصل أخطاء React نفسها كرموز قصيرة "Minified React error". شغّل تطبيقك في وضع التطوير لتقرأ النص الكامل.
دع أداة الفحص تفرضها
تفحص eslint-plugin-react-hooks القاعدتين أثناء الكتابة، وتفحص أيضًا مصفوفات اعتماديات التأثيرات. يتضمنها قالب React في Vite وإعداد ESLint في Next.js. وفي إعداد ESLint المسطح، فعّل إعدادها الموصى به:
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
export default defineConfig([reactHooks.configs.flat.recommended]);
القاعدتان الأساسيتان هما react-hooks/rules-of-hooks (خطأ) وreact-hooks/exhaustive-deps (تحذير). ومنذ الإصدار 7 يفعّل الإعداد أيضًا قواعد آتية من React Compiler، مثل react-hooks/purity وreact-hooks/refs، اللتين تنبهان إلى العرض غير النقي وقراءة المراجع أثناء العرض. ولاختيار القواعد واحدة واحدة، سجّل الإضافة على شكل plugins: { 'react-hooks': reactHooks } واسرد القواعد التي تريدها.
تجد الإضافة الخطافات بأسمائها: أي دالة يبدأ اسمها بـ use يليها حرف كبير تُعامل كخطاف، ولا يجوز أن تستدعيها إلا المكوّنات (أسماء بحرف كبير) ودوال use الأخرى. ولهذا يجب أن تستخدم الخطافات المخصصة البادئة.
الاستثناء: use
الخطاف use، الذي أُضيف في React 19، يقرأ وعدًا أو سياقًا، وخلافًا لكل خطاف آخر يجوز استدعاؤه داخل شرط أو حلقة. لكن ما زال يجب استدعاؤه أثناء العرض، داخل مكوّن أو خطاف.
لا يتغير لون إلا العنوان ذي السمة عندما تبدّل السمة. جرّب الشيء نفسه مع useContext داخل if فتنبّه إليه أداة الفحص. تتناول صفحة الخطاف use قراءة الوعود به.
الأسئلة الشائعة
ما هي قواعد الخطافات؟
قاعدتان. لا تستدعِ الخطافات إلا في المستوى الأعلى من مكوّن دالة أو خطاف مخصص، ولا تستدعها أبدًا داخل شروط أو حلقات أو دوال متداخلة أو بعد إرجاع مبكر. ولا تستدعِ الخطافات إلا من مكوّنات الدوال أو الخطافات المخصصة، لا من دوال عادية أو مكوّنات أصناف.
لماذا لا يمكن استدعاء الخطافات بشرط؟
لا تعرف React خطافاتك بأسمائها. بل تخزن قيمها في قائمة وتعيدها بترتيب حدوث الاستدعاءات. إذا تخطى شرط استدعاءً، يقرأ كل خطاف لاحق القيمة التي كانت للخطاف الذي قبله.
ما معنى الرسالة "Rendered more hooks than during the previous render"؟
استدعى عرضٌ خطافات أكثر من العرض الذي قبله، عادة لأن خطافًا يقع داخل if أو بعد return مبكر جرى بشكل مختلف هذه المرة. انقل كل خطاف إلى ما قبل الشرط وضع الشرط داخل الخطاف بدلًا من ذلك.
كيف أصلح الرسالة "Invalid hook call"؟
تحقق من ثلاثة أمور: أن الخطاف يعمل بينما تعرض React مكوّن دالة (لا في معالج حدث، ولا في دالة تأثير، ولا في مكوّن صنف، ولا على مستوى الوحدة)، وأن إصداري react وreact-dom متطابقان، وأن في التطبيق نسخة واحدة فقط من React (يسردها npm ls react).
هل يمكن استدعاء الخطاف use بشرط؟
نعم. الخطاف use (React 19) هو الاستثناء الوحيد: يمكنك استدعاؤه داخل كتل if والحلقات. لكن ما زال يجب استدعاؤه داخل مكوّن أو خطاف، لا في معالج حدث.