نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
المقالات/React
React

Custom Hooks في React: منطق قابل لإعادة الاستخدام دون تكرار الكود أو الجنون

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

فريق نوفيل١٤ يوليو ٢٠٢٦9 دقائق قراءة١٨ مشاهدة

في أحد مشاريع الإنتاج الكبيرة لشركة ناشئة في مجال الصحة الرقمية، كان لدينا مكون واحد فقط مسؤول عن إدارة بيانات المريض: تحميل السجل الطبي، تحديث الحالة، وإرسال الإشعارات للمستشفى. بعد ثلاثة أشهر، أصبح هذا المكون ملفاً ضخماً يتجاوز ٨٠٠ سطر، وكل محاولة لتعديل أي جزء منه كانت تنتهي بكارثة: إما أن ينكسر شيء آخر، أو أن يتوقف الـ Event Loop عن الاستجابة لأننا نسينا أن نوقف الـ setInterval داخل useEffect. المشكلة لم تكن في الكود نفسه، بل في أننا كررنا نفس المنطق في خمسة مكونات مختلفة، وكل مرة ننسى تعديل شيء صغير، مثل تنظيف الـ listeners أو إدارة الـ loading state بشكل صحيح. هنا ظهرت الحاجة لـ Custom Hooks، ليس كخيار جميل، بل كضرورة هندسية لإنقاذ المشروع من الفوضى.

الـ Custom Hooks في React ليست مجرد طريقة لتنظيم الكود، بل هي أداة قوية لإعادة استخدام منطق الحالة والسلوك بطريقة تحافظ على أداء التطبيق وتقلل من الأخطاء البشرية. عندما تستخدم useState و useEffect داخل مكون، فأنت في الواقع تكتب منطقاً مرتبطاً بوظيفة محددة، ولكن عندما تنقل هذا المنطق إلى Custom Hook، فأنت تحول هذا الرابط إلى وحدة مستقلة يمكن إعادة استخدامها في أي مكان آخر. الفرق هنا ليس فقط في التنظيم، بل في كيفية تعامل React مع الذاكرة والمعالج: عندما تكرر نفس الكود في مكونات متعددة، فأنت تضاعف عدد الـ re-renders والـ effects التي تعمل في الخلفية، مما يؤدي إلى استهلاك غير ضروري للذاكرة والمعالج. أما مع Custom Hook، فأنت تضمن أن المنطق يعمل مرة واحدة فقط، حتى لو استخدمته في عشرة مكونات مختلفة.

لماذا لا يكفي مجرد استخراج الدوال؟

الكثير من المطورين يعتقدون أن حل مشكلة تكرار الكود هو ببساطة استخراج الدوال المشتركة إلى ملف منفصل واستدعائها داخل المكونات. هذا النهج يعمل في بعض الحالات البسيطة، مثل حساب مجموع أو تنسيق تاريخ، لكنه يفشل فشلاً ذريعاً عندما يتعلق الأمر بمنطق الحالة أو الـ side effects. السبب هو أن الدوال العادية لا تستطيع استخدام الـ Hooks الخاصة بـ React، مثل useState أو useEffect، لأنها يجب أن تُستدعى داخل مكون وظيفي أو داخل Custom Hook آخر. هذا يعني أنك إذا حاولت استخراج منطق يحتوي على useState إلى دالة عادية، فستحصل على خطأ: "Invalid hook call. Hooks can only be called inside of the body of a function component."

الـ Custom Hooks تحل هذه المشكلة لأنها ببساطة دوال عادية تبدأ باسم use، مما يسمح لها باستخدام جميع الـ Hooks الخاصة بـ React. ولكن الفائدة الحقيقية ليست فقط في تجاوز هذا الخطأ، بل في كيفية تعامل React مع هذه الدوال خلف الكواليس. عندما تستخدم Custom Hook داخل مكون، فإن React يعامل كل استدعاء لهذا Hook ككيان مستقل، مما يعني أن كل مكون يحصل على نسخته الخاصة من الحالة والـ effects. هذا السلوك مشابه تماماً لكيفية عمل الـ Hooks الأصلية مثل useState، حيث أن كل استدعاء لـ useState داخل مكون يعطي هذا المكون حالة مستقلة عن المكونات الأخرى.

javascript
// مثال سيئ: دالة عادية لا تستطيع استخدام Hooks
function fetchData(url) {
 // ❌ خطأ: لا يمكن استخدام useState هنا
 const [data, setData] = useState(null);
 useEffect(() => {
 fetch(url).then(res => res.json()).then(setData);
 }, [url]);
 return data;
}

// مثال جيد: Custom Hook
function useFetch(url) {
 const [data, setData] = useState(null);
 const [loading, setLoading] = useState(true);
 const [error, setError] = useState(null);

 useEffect(() => {
 let isMounted = true;
 setLoading(true);
 fetch(url)
 .then(res => res.json())
 .then(data => {
 if (isMounted) setData(data);
 })
 .catch(err => {
 if (isMounted) setError(err);
 })
 .finally(() => {
 if (isMounted) setLoading(false);
 });

 return () => {
 isMounted = false;
 };
 }, [url]);

 return { data, loading, error };
}

مثال عملي: إدارة حالة البحث مع تجنب الـ Memory Leaks

في أحد مشاريع التجارة الإلكترونية التي عملت عليها، كان لدينا مكون بحث متقدم يسمح للمستخدمين بتصفية المنتجات بناءً على عدة معايير مثل السعر، التصنيف، والتقييم. المشكلة كانت في أن كل مرة يقوم المستخدم بتغيير أي فلتر، كنا نرسل طلباً جديداً للسيرفر، ولكن إذا قام المستخدم بتغيير الفلتر بسرعة قبل أن ينتهي الطلب السابق، كنا نحصل على نتائج غير متوقعة أو حتى خطأ في واجهة المستخدم لأن الـ state لم يكن متزامناً مع الاستجابة الأخيرة. الحل التقليدي كان استخدام علم مثل isLatest داخل useEffect، ولكن هذا الحل كان يتكرر في ثلاثة مكونات مختلفة، وكل مرة كنا ننسى تعديل شيء صغير، مثل تنظيف الـ abort controller أو إدارة الـ loading state بشكل صحيح.

الـ Custom Hook هنا كان الحل الأمثل لأنه سمح لنا بتوحيد منطق البحث في مكان واحد، وضمان أن كل مكون يستخدم نفس المنطق دون تكرار. الميزة الإضافية كانت في كيفية تعامل هذا Hook مع الـ Memory Leaks: باستخدام AbortController، كنا نستطيع إلغاء الطلبات السابقة قبل إرسال طلبات جديدة، مما يمنع تراكم الطلبات في الخلفية واستهلاك موارد غير ضروري. هذا النوع من التفاصيل الصغيرة هو ما يميز الكود الجيد عن الكود الذي يعمل فقط، وهو ما يجعل Custom Hooks أداة لا غنى عنها في المشاريع الكبيرة.

javascript
function useSearch(query, opti {}) {
 const { debounceTime = 300, initialData = null } = options;
 const [results, setResults] = useState(initialData);
 const [loading, setLoading] = useState(false);
 const [error, setError] = useState(null);
 const abortControllerRef = useRef(null);

 useEffect(() => {
 if (!query) {
 setResults(initialData);
 return;
 }

 const timer = setTimeout(() => {
 if (abortControllerRef.current) {
 abortControllerRef.current.abort();
 }

 const abortController = new AbortController();
 abortControllerRef.current = abortController;

 setLoading(true);
 setError(null);

 fetch(`/api/search?q=${encodeURIComponent(query)}`, {
 signal: abortController.signal
 })
 .then(res => res.json())
 .then(data => {
 if (!abortController.signal.aborted) {
 setResults(data);
 }
 })
 .catch(err => {
 if (err.name !== 'AbortError') {
 setError(err);
 }
 })
 .finally(() => {
 if (!abortController.signal.aborted) {
 setLoading(false);
 }
 });
 }, debounceTime);

 return () => {
 clearTimeout(timer);
 if (abortControllerRef.current) {
 abortControllerRef.current.abort();
 }
 };
 }, [query, debounceTime]);

 return { results, loading, error };
}

كيف يمنع هذا الكود الـ Memory Leaks؟

في الكود السابق، هناك ثلاث آليات رئيسية لمنع الـ Memory Leaks: أولاً، استخدام AbortController لإلغاء الطلبات السابقة قبل إرسال طلبات جديدة، مما يمنع تراكم الطلبات في الخلفية واستهلاك الذاكرة والمعالج بشكل غير ضروري. ثانياً، استخدام علم isMounted بشكل غير مباشر عبر التحقق من حالة الـ signal.aborted داخل الـ then و finally، مما يضمن أننا لا نقوم بتحديث الـ state بعد أن يكون المكون قد تم إلغاء تحميله. ثالثاً، تنظيف الـ timer و الـ abortController داخل الـ cleanup function الخاصة بـ useEffect، مما يضمن عدم ترك أي موارد معلقة بعد إلغاء تحميل المكون.

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


الـ Custom Hooks مع الـ Context API: قوة مركبة

في أحد مشاريع إدارة المهام التي عملت عليها، كان لدينا مشكلة شائعة: كيف نمرر بيانات المستخدم الحالية (مثل الاسم، الصورة، والإعدادات) إلى جميع المكونات دون الحاجة إلى تمريرها يدوياً عبر الـ props في كل مستوى من شجرة المكونات. الحل التقليدي كان استخدام Context API، ولكن المشكلة كانت في أن كل مكون يحتاج إلى استدعاء useContext للحصول على البيانات، مما يؤدي إلى إعادة تحميل المكونات بشكل غير ضروري عند تغيير أي قيمة في الـ Context، حتى لو لم تكن هذه القيمة ذات صلة بالمكون.

الحل الأمثل هنا كان دمج الـ Custom Hooks مع الـ Context API لإنشاء طبقة تجريدية تسمح للمكونات بالحصول على البيانات التي تحتاجها فقط، دون إعادة تحميل عند تغيير بيانات أخرى. الفكرة بسيطة: بدلاً من أن يستدعي كل مكون useContext مباشرة، نقوم بإنشاء Custom Hook مثل useUser الذي يقوم باستدعاء useContext داخلياً ويعيد فقط البيانات التي يحتاجها المكون. هذا النهج لا يقلل فقط من عدد الـ re-renders، بل يجعل الكود أكثر قابلية للصيانة لأنه يخفي تفاصيل تنفيذ الـ Context خلف واجهة بسيطة.

javascript
// UserContext.js
import { createContext, useContext } from 'react';

const UserC createContext(null);

export function UserProvider({ children, user }) {
 return (
 <UserContext.Provider value={user}>
 {children}
 </UserContext.Provider>
 );
}

export function useUser() {
 const user = useContext(UserContext);
 if (user === null) {
 throw new Error('useUser must be used within a UserProvider');
 }
 return user;
}

// في مكون آخر
function UserProfile() {
 const user = useUser(); // يحصل فقط على البيانات التي يحتاجها
 return <div>{user.name}</div>;
}

لماذا هذا النهج أفضل من استخدام useContext مباشرة؟

عندما يستخدم المكون useContext مباشرة، فإنه يعيد كامل الـ value التي تم تمريرها إلى الـ Provider، مما يعني أن أي تغيير في أي جزء من هذه القيمة يؤدي إلى إعادة تحميل المكون، حتى لو كان التغيير في جزء غير مرتبط بالمكون. على سبيل المثال، إذا كان الـ Context يحتوي على كائن مثل { user: { name, avatar }, settings: { theme, language } }، فإن تغيير theme سيؤدي إلى إعادة تحميل جميع المكونات التي تستخدم هذا الـ Context، حتى تلك التي تحتاج فقط إلى بيانات المستخدم. أما مع Custom Hook مثل useUser، فإن المكون يحصل فقط على البيانات التي يحتاجها، مما يقلل من عدد الـ re-renders بشكل كبير.

بالإضافة إلى ذلك، فإن استخدام Custom Hook يجعل الكود أكثر قابلية للصيانة لأنه يخفي تفاصيل تنفيذ الـ Context خلف واجهة بسيطة. إذا قررت في المستقبل تغيير هيكلية الـ Context أو حتى استبداله بـ Zustand أو Redux، فلن تحتاج إلى تعديل جميع المكونات التي تستخدم البيانات، بل ستعدل فقط الـ Custom Hook. هذا النوع من التجريد هو ما يجعل الفرق بين الكود الذي يمكن صيانته والكود الذي يصبح كابوساً عند كل تعديل.


الفخاخ الشائعة وكيفية تجنبها

على الرغم من أن الـ Custom Hooks أداة قوية، إلا أنها ليست خالية من المشاكل. أحد أكثر الأخطاء شيوعاً هو محاولة استخدام Custom Hook داخل حلقة أو شرط، مما يؤدي إلى خطأ: "Rendered fewer hooks than expected." السبب هو أن React يعتمد على ترتيب استدعاء الـ Hooks لتحديد أي حالة أو effect ينتمي إلى أي استدعاء، فإذا قمت بتغيير عدد الـ Hooks بين الـ renders، فإن React سيفقد التزامن وسيؤدي ذلك إلى سلوك غير متوقع أو أخطاء.

javascript
// ❌ خطأ شائع: استخدام Custom Hook داخل شرط
function Component({ shouldFetch }) {
 if (shouldFetch) {
 const { data } = useFetch('https://api.example.com'); // خطأ
 return <div>{data}</div>;
 }
 return null;
}

// ✅ الحل الصحيح: استدعاء الـ Hook دائماً وإدارة الشرط داخله
function Component({ shouldFetch }) {
 const { data } = useFetch(shouldFetch ? 'https://api.example.com' : null);
 return shouldFetch ? <div>{data}</div> : null;
}

مشكلة أخرى شائعة هي تجاهل تنظيف الـ effects داخل الـ Custom Hooks، مما يؤدي إلى الـ Memory Leaks. على سبيل المثال، إذا كان الـ Hook يقوم بإعداد مستمع للأحداث أو فتح اتصال بـ WebSocket، فيجب أن يقوم بتنظيف هذه الموارد داخل الـ cleanup function الخاصة بـ useEffect. تجاهل هذا التنظيف يؤدي إلى تراكم المستمعين أو الاتصالات في الخلفية، مما يستهلك الذاكرة والمعالج بشكل غير ضروري وقد يؤدي إلى سلوك غير متوقع عند إلغاء تحميل المكون.

  • •دائماً استدعِ الـ Custom Hooks في أعلى مستوى المكون، وليس داخل حلقات أو شروط.
  • •استخدم useRef لتخزين القيم التي لا تريد أن تؤدي إلى إعادة تحميل المكون عند تغييرها، مثل الـ AbortController أو الـ timers.
  • •تأكد من تنظيف جميع الـ effects و الـ listeners داخل الـ cleanup function لمنع الـ Memory Leaks.
  • •استخدم أدوات مثل React DevTools و Why Did You Render لتتبع عدد الـ re-renders والتأكد من أن الـ Custom Hooks تعمل بكفاءة.
  • •اختبر الـ Custom Hooks بشكل مستقل باستخدام مكتبات مثل @testing-library/react-hooks للتأكد من أنها تعمل كما هو متوقع في جميع السيناريوهات.

متى يجب تجنب الـ Custom Hooks؟

على الرغم من أن الـ Custom Hooks أداة قوية، إلا أنها ليست الحل الأمثل في جميع الحالات. إذا كان المنطق الذي تريد إعادة استخدامه بسيطاً جداً ولا يحتوي على حالة أو effects، فقد يكون من الأفضل استخدام دالة عادية بدلاً من Custom Hook. على سبيل المثال، إذا كنت تريد حساب مجموع عددين أو تنسيق تاريخ، فلا حاجة لاستخدام Custom Hook لأن الدوال العادية ستفي بالغرض دون إضافة تعقيد غير ضروري.

أيضاً، إذا كان المنطق الذي تريد إعادة استخدامه مرتبطاً بواجهة المستخدم بشكل وثيق، مثل مكون زر مخصص أو قائمة منسدلة، فقد يكون من الأفضل إنشاء مكون React عادي بدلاً من Custom Hook. السبب هو أن المكونات توفر مرونة أكبر في التحكم في الـ rendering والسلوك، بينما الـ Custom Hooks مخصصة لإعادة استخدام منطق الحالة والسلوك فقط. في النهاية، القرار يعتمد على طبيعة المشكلة التي تحاول حلها: إذا كانت المشكلة تتعلق بالحالة أو الـ side effects، فاستخدم Custom Hook، أما إذا كانت تتعلق بواجهة المستخدم، فاستخدم مكون.

خلاصة المهندس: نصيحة واحدة تغني عن ألف سطر

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

React Hooks Custom Hooks JavaScript Frontend Development Web Performance

التعليقات

العودة للمقالات
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر