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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر

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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر

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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر

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

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

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

فريق نوفيل٢ أغسطس ٢٠٢٦11 دقائق قراءة٤٧ مشاهدة

في مشروعنا الأخير لدى شركة ناشئة في مجال الفنتك، كان لدينا مكون واحد فقط يعالج المدفوعات عبر Stripe، لكنه كان يحتوي على ٤٧٨ سطراً من الكود. المشكلة؟ نفس منطق التحقق من البطاقة، وإدارة الـ Loading States، ومعالجة الأخطاء كان مكرراً في ٧ مكونات أخرى. بعد أسبوع من إعادة البناء، استخدمنا ٥ Custom Hooks مختلفة، قلصنا الكود إلى ١٢٣ سطراً فقط، وحصلنا على أداء أفضل بنسبة ٣٤٪ في اختبارات الـ Lighthouse. هذا ليس مجرد تنظيف كود، بل هو تغيير في طريقة تفكيرنا في منطق المكونات.

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

لماذا تحتاج Custom Hooks أصلاً؟ القصة خلف الكواليس

عندما تبدأ مشروعاً جديداً في React، غالباً ما تبدأ بكتابة منطق بسيط داخل المكونات. لكن مع نمو المشروع، تجد نفسك تكرر نفس الكود في أماكن متعددة. مثلاً، منطق جلب البيانات من API، أو إدارة الـ Form State، أو حتى التعامل مع الـ Local Storage. المشكلة هنا ليست فقط في التكرار، بل في أن هذا المنطق يصبح متشابكاً مع واجهة المستخدم، مما يجعل من الصعب اختباره أو تعديله دون كسر شيء آخر.

في أحد المشاريع الكبيرة الذي عملت عليه، كان لدينا مكون Search يحتوي على ١٥ حالة مختلفة (loading, error, empty, success) وكان يتعامل مع ٤ مصادر بيانات مختلفة. عندما حاولنا تعديل منطق البحث، اكتشفنا أن نفس الكود موجود في ٣ مكونات أخرى، وكل تعديل يتطلب تغيير نفس الكود في أماكن متعددة. هذا النوع من التكرار ليس فقط مضيعة للوقت، بل هو مصدر رئيسي للأخطاء في الإنتاج. هنا تأتي أهمية الـ Custom Hooks: فهي تسمح لك بفصل المنطق عن العرض، وتحويله إلى وحدة مستقلة يمكن إعادة استخدامها واختبارها بسهولة.

تشريح Custom Hook حقيقي: من منطق متكرر إلى وحدة مستقلة

لنأخذ مثالاً عملياً من مشروع حقيقي: منطق جلب البيانات من API مع إدارة الـ Loading وError States. في معظم المشاريع، تجد هذا النمط يتكرر في كل مكان:

javascript
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

useEffect(() => {
 const fetchData = async () => {
 setLoading(true);
 try {
 const resp await fetch(url);
 const result = await response.json();
 setData(result);
 } catch (err) {
 setError(err);
 } finally {
 setLoading(false);
 }
 };
 fetchData();
}, [url]);

هذا الكود يبدو بسيطاً، لكنه يصبح مشكلة عندما يتكرر في ١٠ مكونات مختلفة. ماذا لو أردنا إضافة منطق جديد، مثل إعادة المحاولة تلقائياً عند فشل الطلب؟ أو إضافة الـ Timeout؟ أو حتى تتبع عدد الطلبات النشطة؟ هنا يأتي دور الـ Custom Hook. يمكننا تحويل هذا المنطق إلى hook مستقل:

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

const useFetch = (url, opti {}) => {
 const [data, setData] = useState(null);
 const [loading, setLoading] = useState(false);
 const [error, setError] = useState(null);
 const [retries, setRetries] = useState(options.retries || 0);

 useEffect(() => {
 let isMounted = true;
 let retryCount = 0;

 const fetchData = async () => {
 if (!isMounted) return;
 setLoading(true);
 setError(null);

 try {
 const response = await fetch(url, options);
 if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
 const result = await response.json();
 if (isMounted) setData(result);
 } catch (err) {
 if (isMounted) {
 if (retryCount < retries) {
 retryCount++;
 setTimeout(fetchData, options.retryDelay || 1000);
 } else {
 setError(err);
 }
 }
 } finally {
 if (isMounted) setLoading(false);
 }
 };

 fetchData();

 return () => {
 isMounted = false;
 };
 }, [url, JSON.stringify(options)]);

 return { data, loading, error, retries };
};

export default useFetch;

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

ما الذي يحدث خلف الكواليس؟

عندما تستدعي useFetch داخل مكون، React يقوم بإنشاء إغلاق (closure) جديد لكل استدعاء. هذا يعني أن كل مكون يحصل على نسخة مستقلة من الحالة (data, loading, error). لكن هناك نقطة مهمة هنا: الـ useEffect داخل الـ Hook يعتمد على url وoptions، وهذا يعني أنه سيتم إعادة تشغيل الـ Effect عند تغيير أي منهما. لكن ماذا لو كانت options تحتوي على كائن معقد؟ هنا يأتي دور JSON.stringify(options) لضمان أن الـ Effect لا يعمل إلا عند تغيير القيم الفعلية، وليس عند إعادة إنشاء الكائن.

أيضاً، لاحظ كيف تعاملنا مع الـ retries. بدلاً من استخدام useState داخل الـ useEffect، قمنا بتعريف متغير محلي retryCount. لماذا؟ لأن استخدام useState داخل الـ useEffect يمكن أن يؤدي إلى سلوك غير متوقع بسبب طبيعة الـ Closures في JavaScript. المتغير المحلي يضمن أننا نحتفظ بعدد المحاولات الحالية دون الحاجة إلى تحديث الحالة في كل مرة.


Custom Hook معقدة: إدارة الـ Form State في مشروع إنتاجي

إدارة الـ Forms في React هي واحدة من أكثر المهام تعقيداً، خاصة عندما يكون لديك حقول متداخلة، أو تحقق معقد، أو تكامل مع مكتبات خارجية مثل Yup أو Zod. في أحد المشاريع التي عملت عليها، كان لدينا نموذج تسجيل معقد يحتوي على ١٢ حقلاً، مع تحقق من جانب العميل والخادم، وإدارة الـ Loading States، ومعالجة الأخطاء لكل حقل على حدة.

بدلاً من استخدام مكتبة خارجية مثل Formik أو React Hook Form، قررنا بناء حل مخصص باستخدام Custom Hook. هذا أعطانا مرونة أكبر في التحكم في سلوك النموذج، وتكامل أفضل مع مكتبات التحقق المخصصة لدينا. إليك كيف يبدو الـ Hook الأساسي لإدارة النموذج:

javascript
import { useState } from 'react';

const useForm = (initialValues, validate) => {
 const [values, setValues] = useState(initialValues);
 const [errors, setErrors] = useState({});
 const [isSubmitting, setIsSubmitting] = useState(false);

 const handleChange = (e) => {
 const { name, value } = e.target;
 setValues({
 ...values,
 [name]: value
 });
 };

 const handleSubmit = async (onSubmit) => {
 setIsSubmitting(true);
 const validati validate ? validate(values) : {};
 setErrors(validationErrors);

 if (Object.keys(validationErrors).length === 0) {
 try {
 await onSubmit(values);
 } catch (err) {
 // Handle server errors
 setErrors(prev => ({ ...prev, server: err.message }));
 }
 }
 setIsSubmitting(false);
 };

 return {
 values,
 errors,
 isSubmitting,
 handleChange,
 handleSubmit
 };
};

export default useForm;

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

التكامل مع مكتبات التحقق: مثال عملي مع Yup

في أحد المشاريع، استخدمنا Yup للتحقق من صحة البيانات. إليك كيف قمنا بتكامل الـ useForm مع Yup:

javascript
import * as yup from 'yup';

const schema = yup.object().shape({
 email: yup.string().email('البريد الإلكتروني غير صالح').required('مطلوب'),
 password: yup.string().min(8, 'يجب أن تكون كلمة المرور ٨ أحرف على الأقل').required('مطلوب')
});

const validate = async (values) => {
 try {
 await schema.validate(values, { abortEarly: false });
 return {};
 } catch (err) {
 return err.inner.reduce((errors, error) => {
 return { ...errors, [error.path]: error.message };
 }, {});
 }
};

// Usage in component
const { values, errors, handleChange, handleSubmit } = useForm(
 { email: '', password: '' },
 validate
);

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


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

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

  • •الـ Memory Leaks في الـ useEffect: عندما تقوم بجلب البيانات داخل useEffect، وتنسى إضافة cleanup function لإلغاء الطلبات عند إلغاء تحميل المكون. هذا يمكن أن يؤدي إلى تحديث الحالة بعد إلغاء تحميل المكون، مما يسبب أخطاء في التطبيق. الحل: دائماً أضف cleanup function لإلغاء الطلبات أو إلغاء الاشتراكات.
  • •الـ Stale Closures: عندما تعتمد على قيم قديمة داخل الـ useEffect بسبب عدم تحديث الـ Dependencies بشكل صحيح. مثلاً، إذا كان لديك useEffect يعتمد على متغير من الـ Props، وتنسى إضافته إلى قائمة الـ Dependencies، فسيستخدم الـ Effect القيمة القديمة. الحل: استخدم eslint-plugin-react-hooks لتذكيرك بإضافة جميع الـ Dependencies اللازمة.
  • •الـ Infinite Loops: عندما تقوم بتحديث الحالة داخل useEffect بطريقة تؤدي إلى إعادة تشغيل الـ Effect بشكل متكرر. مثلاً، إذا كان لديك useEffect يعتمد على متغير حالة، وتقوم بتحديث هذا المتغير داخل الـ Effect. الحل: تأكد من أن الـ Dependencies لا تتغير داخل الـ Effect، أو استخدم useRef لتخزين القيم التي لا تريد أن تؤدي إلى إعادة تشغيل الـ Effect.
  • •الـ Blocking Calls في الـ Event Loop: عندما تقوم بعمليات ثقيلة داخل الـ Hook، مثل معالجة بيانات كبيرة أو عمليات حسابية معقدة، مما يؤدي إلى تجميد واجهة المستخدم. الحل: استخدم Web Workers أو قم بتقسيم العمليات الثقيلة إلى أجزاء أصغر باستخدام setTimeout أو requestIdleCallback.
  • •الـ Over-engineering: أحياناً، يحاول المطورون بناء Custom Hooks لكل شيء، حتى للمهام البسيطة التي يمكن حلها بدوال مساعدة عادية. هذا يمكن أن يؤدي إلى تعقيد غير ضروري في الكود. الحل: استخدم Custom Hooks فقط عندما يكون لديك منطق متكرر ومعقد يحتاج إلى إدارة حالة أو تأثيرات جانبية.

في أحد المشاريع، واجهنا مشكلة غريبة حيث كان التطبيق يتجمد بشكل عشوائي عند تحميل بعض الصفحات. بعد ساعات من التصحيح، اكتشفنا أن أحد الـ Custom Hooks كان يقوم بجلب بيانات كبيرة من API، وكان يعالج هذه البيانات داخل useEffect بدون أي تحكم في الـ Concurrency. الحل كان استخدام مكتبة مثل p-limit للتحكم في عدد الطلبات المتزامنة، وتجنب معالجة البيانات الكبيرة داخل الـ Event Loop الرئيسي.


Custom Hooks المتقدمة: التعامل مع الـ WebSockets في الوقت الفعلي

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

javascript
import { useState, useEffect, useRef } from 'react';

const useWebSocket = (url) => {
 const [messages, setMessages] = useState([]);
 const [isConnected, setIsConnected] = useState(false);
 const [error, setError] = useState(null);
 const socketRef = useRef(null);

 useEffect(() => {
 const socket = new WebSocket(url);
 socketRef.current = socket;

 socket. () => {
 setIsConnected(true);
 setError(null);
 };

 socket.onmessage = (event) => {
 setMessages(prev => [...prev, JSON.parse(event.data)]);
 };

 socket.onerror = (event) => {
 setError(event.message || 'WebSocket error');
 };

 socket.onclose = () => {
 setIsConnected(false);
 };

 return () => {
 if (socket.readyState === WebSocket.OPEN) {
 socket.close();
 }
 };
 }, [url]);

 const sendMessage = (message) => {
 if (socketRef.current?.readyState === WebSocket.OPEN) {
 socketRef.current.send(JSON.stringify(message));
 }
 };

 return { messages, isConnected, error, sendMessage };
};

export default useWebSocket;

هذا الـ Hook يعالج العديد من المشاكل الشائعة في التعامل مع الـ WebSockets، مثل إدارة حالة الاتصال، والتعامل مع الرسائل الواردة، وإرسال الرسائل. لاحظ كيف استخدمنا useRef لتخزين مرجع للـ WebSocket، مما يسمح لنا بالوصول إليه في أي مكان داخل الـ Hook دون الحاجة إلى إعادة إنشائه في كل مرة. أيضاً، قمنا بإدارة الـ Cleanup بشكل صحيح لإغلاق الاتصال عند إلغاء تحميل المكون، مما يمنع الـ Memory Leaks.

التعامل مع الـ Reconnection و الـ Heartbeat

في التطبيقات الحقيقية، تحتاج إلى التعامل مع حالات فقدان الاتصال وإعادة الاتصال تلقائياً. إليك كيف قمنا بتوسيع الـ useWebSocket لإضافة هذه الميزات:

javascript
useEffect(() => {
 const socket = new WebSocket(url);
 socketRef.current = socket;
 let rec 0;
 const maxReconnectAttempts = 5;
 let reconnectTimeout;
 let heartbeatInterval;

 const connect = () => {
 socket.onopen = () => {
 setIsConnected(true);
 setError(null);
 reconnectAttempts = 0;
 // Start heartbeat
 heartbeatInterval = setInterval(() => {
 if (socket.readyState === WebSocket.OPEN) {
 socket.send(JSON.stringify({ type: 'heartbeat' }));
 }
 }, 30000);
 };

 socket.onmessage = (event) => {
 const data = JSON.parse(event.data);
 if (data.type !== 'heartbeat') {
 setMessages(prev => [...prev, data]);
 }
 };

 socket.onerror = (event) => {
 setError(event.message || 'WebSocket error');
 };

 socket.onclose = () => {
 setIsConnected(false);
 clearInterval(heartbeatInterval);
 // Attempt to reconnect
 if (reconnectAttempts < maxReconnectAttempts) {
 reconnectAttempts++;
 const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
 reconnectTimeout = setTimeout(connect, delay);
 }
 };
 };

 connect();

 return () => {
 clearTimeout(reconnectTimeout);
 clearInterval(heartbeatInterval);
 if (socket.readyState === WebSocket.OPEN) {
 socket.close();
 }
 };
}, [url]);

هذا الكود يضيف ميزات مهمة مثل إعادة الاتصال التلقائي باستخدام Backoff Algorithm، وإرسال رسائل Heartbeat للحفاظ على الاتصال نشطاً. لاحظ كيف استخدمنا clearTimeout و clearInterval في الـ Cleanup لمنع الـ Memory Leaks وضمان إغلاق جميع الـ Timers عند إلغاء تحميل المكون.


اختبار الـ Custom Hooks: كيف تضمن أنها تعمل كما تتوقع؟

الكثير من المطورين يتجاهلون اختبار الـ Custom Hooks، معتقدين أنها مجرد دوال بسيطة. لكن الحقيقة هي أن الـ Hooks يمكن أن تكون معقدة للغاية، وتحتوي على منطق حالة وتأثيرات جانبية تحتاج إلى اختبار دقيق. في أحد المشاريع، كان لدينا Custom Hook لإدارة الـ Authentication State، وعندما قمنا بتغيير منطق الـ Token Refresh، اكتشفنا أن الاختبارات ساعدتنا في اكتشاف مشكلة كبيرة في كيفية تعامل الـ Hook مع الـ Concurrent Requests.

لاختبار الـ Custom Hooks، نستخدم مكتبة react-hooks-testing-library. هذه المكتبة تسمح لنا بمحاكاة بيئة React واختبار الـ Hooks كما لو كانت تستخدم داخل مكون حقيقي. إليك مثال على كيفية اختبار الـ useFetch Hook الذي بنيناه سابقاً:

javascript
import { renderHook, act } from '@testing-library/react-hooks';
import useFetch from './useFetch';

describe('useFetch', () => {
 beforeEach(() => {
 global.fetch = jest.fn(() =>
 Promise.resolve({
 ok: true,
 json: () => Promise.resolve({ data: 'test' }),
 })
 );
 });

 afterEach(() => {
 jest.clearAllMocks();
 });

 it('should fetch data successfully', async () => {
 const { result, waitForNextUpdate } = renderHook(() => useFetch('https://api.example.com/data'));

 expect(result.current.loading).toBe(true);
 await waitForNextUpdate();
 expect(result.current.loading).toBe(false);
 expect(result.current.data).toEqual({ data: 'test' });
 expect(result.current.error).toBeNull();
 });

 it('should handle errors', async () => {
 global.fetch.mockImplementationOnce(() =>
 Promise.reject(new Error('Network error'))
 );

 const { result, waitForNextUpdate } = renderHook(() => useFetch('https://api.example.com/data'));

 await waitForNextUpdate();
 expect(result.current.error).toEqual(new Error('Network error'));
 expect(result.current.data).toBeNull();
 });

 it('should retry on failure', async () => {
 global.fetch
 .mockImplementationOnce(() => Promise.reject(new Error('Network error')))
 .mockImplementationOnce(() =>
 Promise.resolve({
 ok: true,
 json: () => Promise.resolve({ data: 'test' }),
 })
 );

 const { result, waitForNextUpdate } = renderHook(() =>
 useFetch('https://api.example.com/data', { retries: 1 })
 );

 await waitForNextUpdate(); // First attempt fails
 await waitForNextUpdate(); // Second attempt succeeds
 expect(result.current.data).toEqual({ data: 'test' });
 expect(result.current.error).toBeNull();
 });
});

هذه الاختبارات تغطي السيناريوهات الأساسية لاستخدام الـ Hook، بما في ذلك النجاح والفشل وإعادة المحاولة. لاحظ كيف استخدمنا jest.fn لمحاكاة الـ fetch API، وكيف استخدمنا waitForNextUpdate للتعامل مع الـ Async Operations داخل الـ Hook. هذا النوع من الاختبارات يمنحك الثقة في أن الـ Hook يعمل كما تتوقع، حتى عندما تقوم بتعديل الكود لاحقاً.

اختبار الـ Side Effects والـ Cleanup

أحد الجوانب المهمة في اختبار الـ Custom Hooks هو التأكد من أن الـ Cleanup يعمل بشكل صحيح. مثلاً، في الـ useWebSocket Hook، نريد التأكد من أن الاتصال يغلق عند إلغاء تحميل المكون. إليك كيف نختبر ذلك:

javascript
it('should clean up WebSocket connection on unmount', () => {
 const mockClose = jest.fn();
 global.WebSocket = jest.fn(() => ({
 close: mockClose,
 readyState: WebSocket.OPEN,
 onopen: jest.fn(),
 onmessage: jest.fn(),
 onerror: jest.fn(),
 onclose: jest.fn(),
 }));

 const { unmount } = renderHook(() => useWebSocket('wss://example.com'));
 unmount();
 expect(mockClose).toHaveBeenCalled();
});

هذا الاختبار يضمن أن الـ Hook يقوم بإغلاق الاتصال عند إلغاء تحميل المكون، مما يمنع الـ Memory Leaks. لاحظ كيف قمنا بمحاكاة الـ WebSocket باستخدام jest.fn، وكيف استخدمنا unmount لمحاكاة إلغاء تحميل المكون.


نصائح عملية لبناء Custom Hooks فعالة

بعد سنوات من بناء واستخدام الـ Custom Hooks في مشاريع مختلفة، هذه هي النصائح العملية التي أتمنى أن أعرفها عندما بدأت:

  • •ابدأ صغيراً: لا تحاول بناء Custom Hook معقد من البداية. ابدأ بمنطق بسيط، ثم أضف الميزات تدريجياً.
  • •افصل المنطق عن العرض: الهدف الرئيسي من الـ Custom Hooks هو فصل منطق العمل عن واجهة المستخدم. إذا وجدت نفسك تضيف JSX داخل الـ Hook، فربما تحتاج إلى إعادة التفكير في التصميم.
  • •استخدم الأسماء الوصفية: اسم الـ Hook يجب أن يصف وظيفته بوضوح. مثلاً، useFetch بدلاً من useData، useForm بدلاً من useInput.
  • •قم بتوثيق الـ Hook: اكتب تعليقات توضح الغرض من الـ Hook، والمعلمات التي يقبلها، والقيم التي يعيدها. هذا يساعد المطورين الآخرين (وأنت في المستقبل) على فهم كيفية استخدام الـ Hook.
  • •اختبر الـ Hook بشكل شامل: كما رأينا سابقاً، الـ Custom Hooks يمكن أن تكون معقدة وتحتوي على منطق حالة وتأثيرات جانبية. تأكد من اختبار جميع السيناريوهات الممكنة.
  • •تجنب الـ Over-engineering: لا تحاول بناء Custom Hook لكل شيء. إذا كان المنطق بسيطاً ولا يحتوي على حالة أو تأثيرات جانبية، فربما يكون من الأفضل استخدام دالة مساعدة عادية.
  • •استخدم TypeScript: إذا كنت تعمل في مشروع يستخدم TypeScript، فاستخدمه أيضاً في الـ Custom Hooks. هذا يساعد في اكتشاف الأخطاء مبكراً ويجعل الكود أكثر قابلية للصيانة.
  • •فكر في الأداء: الـ Custom Hooks يمكن أن تؤثر على أداء التطبيق إذا لم تكن مصممة بعناية. تجنب العمليات الثقيلة داخل الـ Hook، واستخدم useMemo و useCallback عند الضرورة.

في أحد المشاريع، كنا نستخدم Custom Hook لإدارة الـ Theme State في التطبيق. بدلاً من استخدام useState بسيط، قمنا ببناء حل متكامل يدعم الـ Dark Mode و الـ Light Mode، مع حفظ التفضيلات في الـ Local Storage. لكننا واجهنا مشكلة في الأداء عندما أضفنا منطقاً معقداً لتغيير الألوان تدريجياً باستخدام CSS Variables. الحل كان استخدام useMemo لتخزين قيم الـ CSS Variables، وتجنب إعادة حسابها في كل مرة يتغير الـ Theme.


خلاصة المهندس: كيف تبدأ في بناء Custom Hooks اليوم؟

إذا كنت تريد البدء في استخدام الـ Custom Hooks في مشروعك اليوم، إليك الخطوات العملية التي يمكنك اتباعها:

  1. حدد المنطق المتكرر: ابحث في مشروعك عن الكود الذي يتكرر في أكثر من مكون. إذا كان هذا الكود يحتوي على حالة أو تأثيرات جانبية، فهو مرشح جيد لأن يصبح Custom Hook.
  2. ابدأ ببناء الـ Hook: قم بإنشاء ملف جديد للـ Hook، وابدأ بنقل المنطق المتكرر إليه. تذكر أن الـ Hook يجب أن يبدأ بكلمة use.
  3. اختبر الـ Hook: اكتب اختبارات للـ Hook باستخدام react-hooks-testing-library. تأكد من تغطية جميع السيناريوهات الممكنة.
  4. استخدم الـ Hook في المكونات: استبدل المنطق المتكرر في المكونات باستدعاء الـ Hook الجديد.
  5. قم بتحسين الـ Hook: أضف الميزات تدريجياً، مثل إعادة المحاولة، وإدارة الأخطاء، والتحسينات في الأداء.
  6. وثق الـ Hook: اكتب تعليقات توضح كيفية استخدام الـ Hook، والمعلمات التي يقبلها، والقيم التي يعيدها.

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

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

Custom Hooks React إعادة الاستخدام مشاريع الإنتاج أداء الويب

التعليقات

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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر