क्या आपके React components बिना किसी ठोस वजह के बार-बार re-render हो रहे हैं? जब भी आप अपने पैरेंट स्टेट (Parent State) को बदलते हैं, तो क्या आपके चाइल्ड कंपोनेंट्स भी बिना वजह दोबारा रेंडर होने लगते हैं? मेरे प्यारे दोस्तों, अगर आप भी इस समस्या से जूझ रहे हैं, तो चाय की चुस्की लीजिए क्योंकि आज हम ReactJS के एक बहुत ही शक्तिशाली हथियार यानी React useCallback Hook का पोस्टमार्टम करने वाले हैं।
React useCallback Hook क्या है?
सरल शब्दों में कहें तो, useCallback एक ऐसा Hook है जो आपके function के instance (यानी उसके reference) को cache (या memoize) कर लेता है।
जब भी कोई React Component re-render होता है, तो उसके अंदर लिखे गए सभी functions दोबारा से नए सिरे से (re-create) बनते हैं। भले ही उन functions के अंदर का कोड बिल्कुल न बदला हो, लेकिन JavaScript की दुनिया में उनका memory reference बदल जाता है। useCallback का मुख्य काम इसी re-creation को रोकना है जब तक कि उसकी dependencies न बदलें।
चलिए इसे एक आधिकारिक दस्तावेज के माध्यम से भी समझते हैं। जैसा कि Official React Docs में बताया गया है, यह Hook आपके component के renders के बीच में किसी function definition को cache करने की अनुमति देता है।
React में Referential Equality क्या बला है?
दोस्तो, useCallback को समझने से पहले हमें JavaScript की एक बुनियादी अवधारणा को समझना होगा, जिसे Referential Equality कहते हैं।
JavaScript में primitives (जैसे strings, numbers, booleans) की तुलना उनकी values से होती है:
const a = 5;
const b = 5;
console.log(a === b); // Output: true
लेकिन objects, arrays और functions के साथ ऐसा नहीं होता। इनकी तुलना उनके memory location (reference) से होती है:
const functionOne = () => console.log("Hello World");
const functionTwo = () => console.log("Hello World");
console.log(functionOne === functionTwo); // Output: false
भले ही दोनों functions का काम और कोड बिल्कुल एक जैसा है, लेकिन JavaScript इन्हें दो अलग-अलग डिब्बों (memory allocation) में रखता है। इसलिए ये दोनों आपस में बराबर नहीं हैं।
जब भी आपका React Component रेंडर होता है, तो हर बार एक नया function बनता है, जिससे उसका memory address बदल जाता है। अगर आप इस function को किसी child component में props के रूप में पास कर रहे हैं, तो child component को लगता है कि "अरे! नया prop आया है!" और वह बिना वजह दोबारा re-render हो जाता है।
useCallback Hook कैसे काम करता है? (Architecture Flow)
आइए इसे एक सुंदर चित्र के माध्यम से समझते हैं कि बिना useCallback के और useCallback के साथ हमारा React application कैसे व्यवहार करता है।
ध्यान देने वाली बात ये है कि useCallback खुद कोई जादू नहीं करता, बल्कि यह React के internal storage में आपके function को सुरक्षित रख लेता है और हर render पर वही पुराना Reference लौटाता है जब तक कि dependency array की कोई value न बदले।
useCallback Hook का use कब करें? (Real-World Use Cases)
दोस्तो, हर जगह आँख बंद करके useCallback का उपयोग करना आपके application की performance को सुधारने के बजाय उसे और धीमा कर सकता है (क्योंकि Hook को चलाने का भी अपना एक overhead होता है)।
आपको इसका उपयोग केवल निम्नलिखित परिस्थितियों में करना चाहिए:
- चाइल्ड कंपोनेंट्स का Unnecessary Re-rendering रोकना: जब आप किसी child component को props के रूप में function पास कर रहे हों, और वह child component
React.memoसे optimized हो। - Custom Hooks में: यदि आप कोई Custom Hook बना रहे हैं और उसमें कोई function return कर रहे हैं, तो उसे
useCallbackसे wrap करना एक अच्छी practice माना जाता है ताकि consumers उसे बिना डरे dependency array में उपयोग कर सकें। - useEffect की dependency में: अगर आपका function किसी
useEffectके अंदर इस्तेमाल हो रहा है और आप उस function को dependency array में पास कर रहे हैं।
useCallback Hook vs useMemo Hook में क्या अंतर है?
अक्सर नए डेवलपर्स इन दोनों Hooks के बीच भ्रमित हो जाते हैं। दोनों ही memoization का काम करते हैं, लेकिन दोनों में एक बहुत ही बुनियादी अंतर है जिसे आपको हमेशा याद रखना चाहिए।
| Feature | useCallback Hook | useMemo Hook |
|---|---|---|
| मुख्य उद्देश्य (Purpose) | पूरे के पूरे function definition (reference) को cache करना। | किसी function के output (value) को cache करना। |
| क्या return करता है? | एक Memoized Function | एक Memoized Value / Result |
| उपयोग का तरीका (Syntax) | useCallback(() => fn, [deps]) |
useMemo(() => calculateValue(), [deps]) |
useCallback Hook को कैसे Implement करें? (Step-by-Step Practical Code)
चलिए अब सीधे कोडिंग स्क्रीन पर चलते हैं। हम एक ऐसी समस्या का निर्माण करेंगे जहाँ बिना वजह re-render हो रहा हो, और फिर useCallback की मदद से उस समस्या को हल करेंगे।
Step 1: Unoptimized Code (जहाँ समस्या है)
नीचे दिए गए कोड में, हमारे पास एक ParentComponent है और एक ChildComponent (जो React.memo से घिरा हुआ है) है।
import React, { useState } from 'react';
// Child Component जो React.memo का उपयोग कर रहा है
const ChildComponent = React.memo(({ handleClick }) => {
console.log("ChildComponent Rendered! 🔴");
return (
<div style={{ marginTop: '20px', padding: '10px', border: '1px solid #ccc' }}>
<h3>चाइल्ड कंपोनेंट</h3>
<button onClick={handleClick} style={{ padding: '8px 12px', background: '#06b6d4', color: '#fff', border: 'none', borderRadius: '5px', cursor: 'pointer' }}>
चाइल्ड बटन दबाएं
</button>
</div>
);
});
export default function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
// यह function हर render पर दोबारा बनेगा
const handleButtonClick = () => {
console.log("Button clicked!");
};
return (
<div style={{ padding: '24px', maxWidth: '500px', margin: '0 auto' }}>
<h2>Parent Component</h2>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)} style={{ padding: '8px 12px', background: '#2563eb', color: '#fff', border: 'none', borderRadius: '5px', cursor: 'pointer', marginRight: '10px' }}>
Increment Count
</button>
<div style={{ marginTop: '15px' }}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="यहाँ कुछ टाइप करें..."
style={{ padding: '8px', width: '100%', boxSizing: 'border-box' }}
/>
</div>
<ChildComponent handleClick={handleButtonClick} />
</div>
);
}
समस्या क्या है? जब भी आप "Increment Count" बटन दबाते हैं या input box में कुछ टाइप करते हैं, तो ParentComponent का state बदलता है और वह re-render होता है। इसके कारण handleButtonClick का एक नया instance बनता है।
यद्यपि ChildComponent को React.memo से wrap किया गया है, फिर भी वह console में "ChildComponent Rendered! 🔴" छापेगा, क्योंकि उसे मिलने वाला handleClick prop हर बार नए reference के साथ आ रहा है।
Step 2: Optimized Code (useCallback के साथ समाधान)
अब हम इस समस्या को useCallback का उपयोग करके हल करेंगे। ध्यान दें कि हम कैसे handleButtonClick को memoize करते हैं।
import React, { useState, useCallback } from 'react';
// Child Component जो React.memo का उपयोग कर रहा है
const ChildComponent = React.memo(({ handleClick }) => {
console.log("ChildComponent Rendered! 🟢 (Optimized)");
return (
<div style={{ marginTop: '20px', padding: '10px', border: '1px solid #ccc' }}>
<h3>चाइल्ड कंपोनेंट</h3>
<button onClick={handleClick} style={{ padding: '8px 12px', background: '#06b6d4', color: '#fff', border: 'none', borderRadius: '5px', cursor: 'pointer' }}>
चाइल्ड बटन दबाएं
</button>
</div>
);
});
export default function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
// useCallback की मदद से function को cache किया गया
const handleButtonClick = useCallback(() => {
console.log("Button clicked!");
}, []); // Empty dependency array का मतलब यह सिर्फ mount के समय बनेगा
return (
<div style={{ padding: '24px', maxWidth: '500px', margin: '0 auto' }}>
<h2>Parent Component (Optimized)</h2>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)} style={{ padding: '8px 12px', background: '#2563eb', color: '#fff', border: 'none', borderRadius: '5px', cursor: 'pointer', marginRight: '10px' }}>
Increment Count
</button>
<div style={{ marginTop: '15px' }}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="यहाँ कुछ टाइप करें..."
style={{ padding: '8px', width: '100%', boxSizing: 'border-box' }}
/>
</div>
<ChildComponent handleClick={handleButtonClick} />
</div>
);
}
जादू देखिए! अब जब आप "Increment Count" दबाएंगे या input box में टाइप करेंगे, तो console में "ChildComponent Rendered! 🟢 (Optimized)" दोबारा प्रिंट नहीं होगा। क्योंकि React जानता है कि handleButtonClick का Reference वही पुराना वाला ही है!
Common Errors और Best Practices क्या हैं?
React में विकास करते समय, अक्सर डेवलपर्स useCallback के साथ कुछ गलतियां कर बैठते हैं जिससे गंभीर बग्स पैदा हो जाते हैं। आइए उनके बारे में चर्चा करते हैं ताकि आप उन गलतियों से बच सकें।
1. Stale Closures (पुरानी State का फंस जाना)
यह सबसे आम error या समस्या है जो डेवलपर्स को परेशान करती है। जब आप dependency array को खाली छोड़ देते हैं, लेकिन function के अंदर किसी state का उपयोग कर रहे होते हैं, तो closure पुरानी state की value को ही याद रखता है।
// ❌ गलत तरीका - Stale Closure Issue
const handleLogCount = useCallback(() => {
console.log("Current count is:", count); // यह हमेशा पुरानी/शुरुआती value ही दिखाएगा
}, []); // count को dependency array में होना चाहिए था!
समाधान: हमेशा उन सभी external variables या states को dependency array में डालें जिनका उपयोग आप function के अंदर कर रहे हैं।
// सही तरीका
const handleLogCount = useCallback(() => {
console.log("Current count is:", count);
}, [count]); // अब count बदलने पर reference अपडेट होगा
2. हर function पर useCallback लगा देना
ध्यान रखें, useCallback मुफ्त नहीं आता। हर बार जब आप इसका उपयोग करते हैं, तो React को एक extra function array allocation और comparison logic चलाना पड़ता है।
अगर आपका Child Component हल्का-फुल्का है और re-render होने में कोई भारी computation नहीं ले रहा है, तो useCallback का उपयोग न करें। साधारण functions को बिना किसी Hook के वैसे ही रहने देना सबसे बेहतर अभ्यास है।
निष्कर्ष और टेकअवे
तो दोस्तों, आज हमने सीखा कि React में performance optimization कितना महत्वपूर्ण है और React useCallback Hook कैसे काम करता है। चलिए इसके मुख्य बातों को संक्षेप में याद कर लेते हैं:
useCallbackका मुख्य काम functions को memoize करना और referential equality को बनाए रखना है।- इसका उपयोग हमेशा
React.memoके साथ किया जाना चाहिए ताकि child components का फालतू रेंडर रोका जा सके। - dependency array का विशेष ध्यान रखें ताकि stale closures जैसी error का सामना न करना पड़े।
- हर साधारण function को
useCallbackमें न लपेटें, इसे केवल भारी ऑपरेशन्स और बड़े कंपोनेंट ट्री के लिए सुरक्षित रखें।


टिप्पणियाँ
एक टिप्पणी भेजें