useCallback
useCallback
एक React हुक है जो आपको री-रेंडर के बीच फंक्शन डेफिनिशन को कैश करने की सुविधा देता है।
const cachedFn = useCallback(fn, dependencies)
Reference
useCallback(fn, dependencies)
अपने कौम्पोनॅन्ट के टॉप लेवल पर फंक्शन डेफिनिशन को री-रेंडर के बीच कैश करने के लिए useCallback
कॉल करें:
import { useCallback } from 'react';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
Parameters
-
fn
: वह फंक्शन जिसे आप कैश करना चाहते हैं। यह किसी भी प्रकार के आर्ग्यूमेंट्स ले सकता है और कोई भी वैल्यू रिटर्न कर सकता है। React पहले रेंडर पर आपका फंक्शन रिटर्न करता है (कॉल नहीं करता!)। अगले रेंडर पर, अगरdependencies
नहीं बदली हैं, तो React वही फंक्शन रिटर्न करता है। यदिdependencies
बदल गई हैं, तो React इस रेंडर में पास किया गया नया फंक्शन रिटर्न करता है और इसे बाद में रियूज़ के लिए स्टोर करता है। React आपका फंक्शन कॉल नहीं करता, बल्कि इसे रिटर्न करता है ताकि आप तय कर सकें कि इसे कब और कैसे कॉल करना है। -
dependencies
: उन सभी रिएक्टिव वैल्यूज़ की सूची जोfn
के कोड में रेफरेंस की गई हैं। रिएक्टिव वैल्यूज़ में प्रॉप्स, स्टेट, और कौम्पोनॅन्ट बॉडी में डायरेक्टली डिक्लेयर किए गए वेरिएबल्स और फंक्शन्स शामिल हैं। यदि आपका लिंटर React के लिए कॉन्फ़िगर है, तो यह वेरिफ़ाई करेगा कि हर रिएक्टिव वैल्यू सही तरीके से डिपेंडेंसी के रूप में दी गई है। डिपेंडेंसीज़ की सूची की संख्या स्थिर होनी चाहिए और इसे इनलाइन,[dep1, dep2, dep3]
की तरह लिखा जाना चाहिए। React प्रत्येक डिपेंडेंसी की तुलना पिछले रेंडर सेObject.is
एल्गोरिदम का उपयोग करके करता है।
Returns
पहले रेंडर पर, useCallback
आपके द्वारा पास किया गया fn
फंक्शन रिटर्न करता है।
अगले रेंडर पर, यह या तो पिछले रेंडर से कैश्ड fn
फंक्शन रिटर्न करता है (यदि डिपेंडेंसीज़ नहीं बदली हैं), या इस रेंडर में पास किया गया नया fn
फंक्शन रिटर्न करता है।
Caveats
useCallback
एक हुक है, इसलिए इसे केवल आपके कौम्पोनॅन्ट या कस्टम हुक के टॉप लेवल पर ही कॉल किया जा सकता है। इसे लूप्स या कंडीशन्स के अंदर कॉल नहीं करना चाहिए। यदि ज़रूरी हो, तो नया कौम्पोनॅन्ट बनाएँ और स्टेट को वहाँ ले जाएँ।- React कैश्ड फंक्शन को तब तक डिस्कार्ड नहीं करता जब तक कोई विशेष कारण न हो। उदाहरण के लिए, डेवलपमेंट मोड में, यदि आप कौम्पोनॅन्ट की फ़ाइल एडिट करते हैं, तो React कैश डिस्कार्ड कर देता है। डेवलपमेंट और प्रोडक्शन दोनों में, यदि कौम्पोनॅन्ट इनिशियल माउंट के दौरान सस्पेंड होता है, तो React कैश डिस्कार्ड कर देता है। भविष्य में, React और फ़ीचर्स जोड़ सकता है जो कैश डिस्कार्ड का लाभ उठाएँ—जैसे कि वर्चुअलाइज़्ड लिस्ट्स के लिए बिल्ट-इन सपोर्ट, जो वर्चुअलाइज़्ड टेबल व्यूपोर्ट से बाहर स्क्रॉल होने वाले आइटम्स के लिए कैश डिस्कार्ड कर सकता है। यदि आप
useCallback
को परफॉर्मेंस ऑप्टिमाइज़ेशन के लिए उपयोग कर रहे हैं, तो यह आपके अपेक्षाओं के अनुरूप होना चाहिए। अन्यथा, स्टेट वेरिएबल या रेफ़ बेहतर विकल्प हो सकता है।
Usage
कौम्पोनॅन्ट की री-रेंडरिंग को स्किप करना
जब आप रेंडर परफॉर्मेंस को ऑप्टिमाइज़ करते हैं, तो चाइल्ड कौम्पोनॅन्ट को पास किए गए फंक्शन्स को कैश करना कभी-कभी ज़रूरी होता है। पहले इसका सिंटैक्स देखें, फिर समझें कि यह कब उपयोगी है।
अपने कौम्पोनॅन्ट के रेंडर के बीच फंक्शन को कैश करने के लिए, उसकी डेफिनिशन को useCallback
हुक में रैप करें:
import { useCallback } from 'react';
function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
// ...
useCallback
को दो चीज़ें पास करनी होंगी:
- फंक्शन डेफिनिशन, जिसे आप रेंडर के बीच कैश करना चाहते हैं।
- डिपेंडेंसीज़ की सूची, जिसमें आपके कौम्पोनॅन्ट के वे सभी वैल्यूज़ शामिल हों जो इस फंक्शन में उपयोग हुए हैं।
पहले रेंडर में, useCallback
से रिटर्न किया गया फंक्शन वही होता है जो आपने पास किया है।
अगले रेंडर में, React आपके द्वारा इस रेंडर में पास की गई डिपेंडेंसीज़ की तुलना पिछले रेंडर से करता है। यदि डिपेंडेंसीज़ नहीं बदली हैं (तुलना Object.is
से होती है), तो useCallback
पिछले रेंडर का फंक्शन रिटर्न करता है। यदि डिपेंडेंसीज़ बदल गई हैं, तो React इस रेंडर में पास किया गया नया फंक्शन रिटर्न करता है।
संक्षेप में, useCallback
फंक्शन को रेंडर के बीच कैश करता है जब तक डिपेंडेंसीज़ न बदलें।
उदाहरण से समझें कि यह कब उपयोगी है।
मान लें आप ProductPage
कौम्पोनॅन्ट से handleSubmit
फंक्शन को ShippingForm
कौम्पोनॅन्ट में पास कर रहे हैं:
function ProductPage({ productId, referrer, theme }) {
// ...
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
आपने नोटिस किया कि theme
प्रॉप को टॉगल करने पर ऐप कुछ देर के लिए फ़्रीज़ हो जाता है। लेकिन यदि आप <ShippingForm />
को JSX से हटा देते हैं, तो ऐप तेज़ हो जाता है। इससे पता चलता है कि ShippingForm
को ऑप्टिमाइज़ करना फ़ायदेमंद हो सकता है।
डिफॉल्ट रूप से, जब कोई कौम्पोनॅन्ट री-रेंडर होता है, तो React उसके सभी चाइल्ड कौम्पोनॅन्ट्स को रिकर्सिवली री-रेंडर करता है। इसलिए जब ProductPage
अलग theme
के साथ री-रेंडर होता है, तो ShippingForm
भी री-रेंडर होता है। यदि चाइल्ड कौम्पोनॅन्ट हल्का है, तो यह सामान्य है। लेकिन यदि आपने चेक किया और पाया कि री-रेंडर धीमा है, तो आप ShippingForm
को memo
से रैप करके बता सकते हैं कि यदि उसके प्रॉप्स पिछले रेंडर जैसे ही हैं, तो री-रेंडर स्किप करें:
import { memo } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// ...
});
इस बदलाव के बाद, ShippingForm
तभी री-रेंडर होगा जब उसके प्रॉप्स पिछले रेंडर से अलग होंगे। यहाँ फंक्शन को कैश करना महत्वपूर्ण हो जाता है! मान लीजिए आपने handleSubmit
को useCallback
के बिना डिक्लेयर किया है:
function ProductPage({ productId, referrer, theme }) {
// हर बार theme बदलेगा, यह एक नया फंक्शन होगा...
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
{/* ...इसलिए ShippingForm के प्रॉप्स हमेशा बदलेंगे और यह हर बार री-रेंडर होगा */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
जावास्क्रिप्ट में, function () {}
या () => {}
हर बार एक नया फंक्शन बनाता है, जैसे {}
हर बार नया ऑब्जेक्ट बनाता है। सामान्यतः यह समस्या नहीं है, लेकिन इसका मतलब है कि ShippingForm
के प्रॉप्स कभी समान नहीं होंगे, और आपकी memo
ऑप्टिमाइज़ेशन काम नहीं करेगी। यही वह स्थिति है जहाँ useCallback
उपयोगी है:
function ProductPage({ productId, referrer, theme }) {
// React को बताएँ कि फंक्शन को रेंडर के बीच कैश करना है...
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // ...ताकि जब तक डिपेंडेंसीज़ न बदलें...
return (
<div className={theme}>
{/* ...ShippingForm को समान प्रॉप्स मिलें और यह री-रेंडर स्किप कर सके */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
handleSubmit
को useCallback
में रैप करने से आप सुनिश्चित करते हैं कि यह रेंडर के बीच वही फंक्शन रहे (जब तक डिपेंडेंसीज़ न बदलें)। आपको फंक्शन को useCallback
से रैप करना ज़रूरी नहीं है जब तक इसके लिए विशिष्ट कारण न हो। इस उदाहरण में कारण यह है कि आप इसे memo
से रैप किए गए कौम्पोनॅन्ट को पास कर रहे हैं, ताकि वह री-रेंडर स्किप कर सके। अन्य कारण इस पेज पर आगे बताए गए हैं।
Deep Dive
useCallback
अक्सर useMemo
के साथ उपयोग होता है। दोनों चाइल्ड कौम्पोनॅन्ट की परफॉर्मेंस ऑप्टिमाइज़ करने में उपयोगी हैं। ये आपको नीचे पास की गई वैल्यूज़ को मेमोइज़ (कैश) करने की सुविधा देते हैं:
import { useMemo, useCallback } from 'react';
function ProductPage({ productId, referrer }) {
const product = useData('/product/' + productId);
const requirements = useMemo(() => { // आपके फंक्शन को कॉल करता है और उसके रिजल्ट को कैश करता है
return computeRequirements(product);
}, [product]);
const handleSubmit = useCallback((orderDetails) => { // फंक्शन को खुद कैश करता है
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
return (
<div className={theme}>
<ShippingForm requirements={requirements} onSubmit={handleSubmit} />
</div>
);
}
दोनों में अंतर यह है कि ये क्या कैश करते हैं:
-
useMemo
आपके फंक्शन को कॉल करने के बाद मिले रिजल्ट को कैश करता है। इस उदाहरण में, यहcomputeRequirements(product)
के रिजल्ट को कैश करता है, ताकि यह तब तक न बदले जब तकproduct
न बदले। इससे आप बिना ज़रूरत केShippingForm
को री-रेंडर किएrequirements
ऑब्जेक्ट को नीचे पास कर सकते हैं। ज़रूरत पड़ने पर, React आपके फंक्शन को रेंडर के दौरान कॉल करके रिजल्ट को फिर से कैलकुलेट करता है। -
useCallback
फंक्शन को खुद कैश करता है। यहuseMemo
की तरह आपके फंक्शन को कॉल नहीं करता। बल्कि, आपके द्वारा दिए गए फंक्शन (handleSubmit
) को कैश करता है, ताकि यह तब तक न बदले जब तकproductId
याreferrer
न बदलें। इससे आप बिना ज़रूरत केShippingForm
को री-रेंडर किएhandleSubmit
को नीचे पास कर सकते हैं। यह कोड तब तक नहीं चलेगा जब तक यूज़र फ़ॉर्म सबमिट न करे।
यदि आप useMemo
से परिचित हैं, तो useCallback
को इस तरह समझ सकते हैं:
// आसान इम्प्लिमेंटेशन (React में कुछ ऐसा होता है)
function useCallback(fn, dependencies) {
return useMemo(() => fn, dependencies);
}
Deep Dive
यदि आपका ऐप इस साइट जैसा है, और ज़्यादातर इंटरैक्शन्स मोटे स्तर के हैं (जैसे पूरे पेज या सेक्शन को रिप्लेस करना), तो सामान्यतः मेमोइज़ेशन की ज़रूरत नहीं होती। लेकिन यदि आपका ऐप ड्रॉइंग एडिटर जैसा है और इंटरैक्शन्स छोटे स्तर के हैं (जैसे शेप्स को मूव करना), तो मेमोइज़ेशन बहुत मददगार हो सकता है।
useCallback
से फंक्शन को कैश करना केवल कुछ स्थितियों में उपयोगी है:
- जब आप इसे किसी
memo
में रैप किए गए कौम्पोनॅन्ट को प्रॉप के रूप में पास करते हैं। आप चाहते हैं कि वैल्यू न बदलने पर री-रेंडरिंग स्किप हो जाए। मेमोइज़ेशन से कौम्पोनॅन्ट तभी री-रेंडर होगा जब उसकी डिपेंडेंसीज़ बदलेंगी। - जब आपका पास किया हुआ फंक्शन किसी हुक की डिपेंडेंसी के रूप में उपयोग होता है। उदाहरण के लिए, किसी दूसरे
useCallback
में रैप किए गए फंक्शन की डिपेंडेंसी के रूप में, याuseEffect
की डिपेंडेंसी के रूप में।
अन्य मामलों में useCallback
से फंक्शन को रैप करने का विशेष फ़ायदा नहीं है। हालाँकि ऐसा करने से बड़ा नुकसान भी नहीं होता, लेकिन कोड कम रीडेबल हो सकता है। साथ ही, हर मेमोइज़ेशन प्रभावी नहीं होता: एक भी “हर बार नई” वैल्यू पूरे कौम्पोनॅन्ट के मेमोइज़ेशन को बेकार कर सकती है।
ध्यान दें कि useCallback
फंक्शन को बनने से नहीं रोकता। आप हमेशा नया फंक्शन बना रहे होते हैं (यह सामान्य है!), लेकिन React इसे इग्नोर करता है और यदि कुछ नहीं बदला तो कैश्ड फंक्शन रिटर्न करता है।
नीचे दिए गए सिद्धांतों को फॉलो करके आप काफी मेमोइज़ेशन को अनावश्यक बना सकते हैं:
-
जब कोई कौम्पोनॅन्ट अन्य कौम्पोनॅन्ट्स को विज़ुअली रैप करता है, तो उसे JSX को चिल्ड्रन के रूप में स्वीकार करने दें। इससे यदि रैपर कौम्पोनॅन्ट अपने स्टेट को अपडेट करता है, तो React जानता है कि उसके चिल्ड्रन को री-रेंडर करने की ज़रूरत नहीं है।
-
लोकल स्टेट का उपयोग करें और ज़रूरत से ज़्यादा स्टेट को ऊपर न ले जाएँ। फ़ॉर्म जैसे ट्रांज़िएंट स्टेट या आइटम होवर की जानकारी जैसे स्टेट्स को कौम्पोनॅन्ट ट्री के सबसे ऊपर या ग्लोबल स्टेट लाइब्रेरी में न रखें।
-
अपने रेंडरिंग लॉजिक को प्योर रखें। यदि कौम्पोनॅन्ट के री-रेंडर होने पर समस्या होती है या विज़ुअल ग्लिच दिखता है, तो यह कौम्पोनॅन्ट का बग है। मेमोइज़ेशन जोड़ने के बजाय बग को ठीक करें।
-
अनावश्यक स्टेट अपडेट्स करने वाले इफ़ेक्ट्स से बचें। React ऐप्स की अधिकतर परफॉर्मेंस समस्याएँ ऐसे इफ़ेक्ट्स से होती हैं जो अपडेट की चेन शुरू करते हैं, जिससे कौम्पोनॅन्ट बार-बार रेंडर होते हैं।
-
इफ़ेक्ट्स में से अनावश्यक डिपेंडेंसीज़ हटाएँ। उदाहरण के लिए, मेमोइज़ेशन की बजाय ऑब्जेक्ट या फंक्शन को इफ़ेक्ट के अंदर या कौम्पोनॅन्ट के बाहर रखना आसान होता है।
यदि इसके बाद भी कोई इंटरैक्शन धीमा लगता है, तो React Developer Tools प्रोफ़ाइलर का उपयोग करें ताकि पता लगे कि कौन-से कौम्पोनॅन्ट्स मेमोइज़ेशन से सबसे ज़्यादा लाभ पा सकते हैं, और ज़रूरत के हिसाब से मेमोइज़ेशन जोड़ें। इन सिद्धांतों को अपनाने से आपके कौम्पोनॅन्ट्स को डिबग करना और समझना आसान होगा। लंबे समय के लिए, हम मेमोइज़ेशन को ऑटोमैटिकली करने पर रिसर्च कर रहे हैं।
Example 1 of 2: useCallback
और memo
के साथ री-रेंडरिंग स्किप करना
इस उदाहरण में, ShippingForm
कौम्पोनॅन्ट को जानबूझकर धीमा किया गया है ताकि आप देख सकें कि जब कोई React कौम्पोनॅन्ट वास्तव में धीमा हो, तब क्या होता है। काउंटर बढ़ाने और थीम को टॉगल करने की कोशिश करें।
काउंटर बढ़ाना धीमा लगता है क्योंकि यह जानबूझकर धीमे किए गए ShippingForm
को री-रेंडर करने पर मजबूर करता है। यह अपेक्षित है क्योंकि काउंटर बदला है, और आपको स्क्रीन पर यूज़र की नई पसंद दिखानी होगी।
अब थीम को टॉगल करने की कोशिश करें। useCallback
और memo
की मदद से, यह तेज़ी से काम करता है, भले ही ShippingForm
धीमा हो! ShippingForm
ने री-रेंडर स्किप कर दिया क्योंकि handleSubmit
फंक्शन में कोई बदलाव नहीं हुआ। handleSubmit
फंक्शन नहीं बदला क्योंकि productId
और referrer
(आपके useCallback
की डिपेंडेंसीज़) पिछले रेंडर से अब तक नहीं बदली हैं।
import { useCallback } from 'react'; import ShippingForm from './ShippingForm.js'; export default function ProductPage({ productId, referrer, theme }) { const handleSubmit = useCallback((orderDetails) => { post('/product/' + productId + '/buy', { referrer, orderDetails, }); }, [productId, referrer]); return ( <div className={theme}> <ShippingForm onSubmit={handleSubmit} /> </div> ); } function post(url, data) { // कल्पना करें कि यह कोई रिक्वेस्ट भेजता है... console.log('POST /' + url); console.log(data); }
मेमोइज़्ड कॉलबैक से स्टेट अपडेट करना
कभी-कभी आपको मेमोइज़्ड कॉलबैक के अंदर पिछले स्टेट के आधार पर स्टेट अपडेट करना पड़ सकता है।
यह handleAddTodo
फंक्शन todos
को डिपेंडेंसी के रूप में निर्दिष्ट करता है क्योंकि यह इससे अगली todos की गणना करता है:
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos([...todos, newTodo]);
}, [todos]);
// ...
आप चाहेंगे कि मेमोइज़्ड फंक्शन्स में कम से कम डिपेंडेंसीज़ हों। यदि आप स्टेट को केवल अगली स्टेट निकालने के लिए पढ़ रहे हैं, तो डिपेंडेंसी हटा सकते हैं। इसके लिए अपडेटर फंक्शन पास करें:
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos(todos => [...todos, newTodo]);
}, []); // ✅ todos डिपेंडेंसी की ज़रूरत नहीं
// ...
यहाँ, todos
को डिपेंडेंसी बनाने और पढ़ने के बजाय, आप React को निर्देश पास करते हैं कि स्टेट को कैसे अपडेट करना है (todos => [...todos, newTodo]
)। अपडेटर फंक्शन्स के बारे में और पढ़ें।
इफ़ेक्ट को बार-बार चलने से रोकना
कभी-कभी आपको इफ़ेक्ट के अंदर फंक्शन कॉल करने की ज़रूरत पड़ सकती है:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
function createOptions() {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
useEffect(() => {
const options = createOptions();
const connection = createConnection(options);
connection.connect();
// ...
यह समस्या पैदा करता है। हर रिएक्टिव वैल्यू को इफ़ेक्ट की डिपेंडेंसी के रूप में डिक्लेयर करना ज़रूरी है। लेकिन यदि आप createOptions
को डिपेंडेंसी के रूप में डिक्लेयर करते हैं, तो यह इफ़ेक्ट को बार-बार चैट रूम से री-कनेक्ट करने का कारण बनेगा:
useEffect(() => {
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🔴 समस्या: यह डिपेंडेंसी हर रेंडर पर बदलती है
// ...
इस समस्या को हल करने के लिए, जिस फंक्शन को इफ़ेक्ट से कॉल करना है, उसे useCallback
में रैप करें:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ केवल जब roomId बदलता है
useEffect(() => {
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ✅ केवल जब createOptions बदलता है
// ...
यह सुनिश्चित करता है कि यदि roomId
समान है, तो रेंडर के बीच createOptions
फंक्शन वही रहता है। हालाँकि, इससे बेहतर यह है कि फंक्शन डिपेंडेंसी की ज़रूरत ही खत्म कर दी जाए। इसके लिए फंक्शन को इफ़ेक्ट के अंदर ले जाएँ:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
function createOptions() { // ✅ useCallback या फंक्शन डिपेंडेंसीज़ की ज़रूरत नहीं!
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ केवल जब roomId बदलता है
// ...
अब आपका कोड सरल हो गया है और इसमें useCallback
की ज़रूरत नहीं है। इफ़ेक्ट डिपेंडेंसीज़ हटाने के बारे में और जानें।
कस्टम हुक को ऑप्टिमाइज़ करना
यदि आप कस्टम हुक लिख रहे हैं, तो सुझाव है कि उसमें रिटर्न होने वाले फंक्शन्स को useCallback
में रैप करें:
function useRouter() {
const { dispatch } = useContext(RouterStateContext);
const navigate = useCallback((url) => {
dispatch({ type: 'navigate', url });
}, [dispatch]);
const goBack = useCallback(() => {
dispatch({ type: 'back' });
}, [dispatch]);
return {
navigate,
goBack,
};
}
यह सुनिश्चित करता है कि आपके हुक के यूज़र्स ज़रूरत पड़ने पर अपने कोड को ऑप्टिमाइज़ कर सकें।
समस्या निवारण
हर बार मेरे कौम्पोनॅन्ट के रेंडर होने पर useCallback
नया फंक्शन रिटर्न करता है
सुनिश्चित करें कि आपने डिपेंडेंसी ऐरे को दूसरे आर्ग्यूमेंट के रूप में निर्दिष्ट किया है!
यदि आप डिपेंडेंसी ऐरे भूल जाते हैं, तो useCallback
हर बार नया फंक्शन रिटर्न करेगा:
function ProductPage({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}); // 🔴 हर बार नया फंक्शन रिटर्न करता है: डिपेंडेंसी ऐरे नहीं दिया
// ...
यह सही वर्ज़न है जिसमें डिपेंडेंसी ऐरे दूसरा आर्ग्यूमेंट है:
function ProductPage({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // ✅ बिना वजह नया फंक्शन नहीं रिटर्न करता
// ...
यदि इससे समस्या हल नहीं होती, तो शायद कोई डिपेंडेंसी पिछले रेंडर से अलग है। आप डिपेंडेंसीज़ को कंसोल में मैन्युअली लॉग करके डिबग कर सकते हैं:
const handleSubmit = useCallback((orderDetails) => {
// ..
}, [productId, referrer]);
console.log([productId, referrer]);
इसके बाद, कंसोल में अलग-अलग रेंडर्स की ऐरे पर राइट-क्लिक करें और दोनों को “Store as a global variable” के रूप में सेव करें। मान लें पहली ऐरे temp1
और दूसरी temp2
के रूप में सेव हुई, तो आप ब्राउज़र कंसोल में चेक कर सकते हैं कि दोनों ऐरे की डिपेंडेंसीज़ समान हैं या नहीं:
Object.is(temp1[0], temp2[0]); // क्या पहली डिपेंडेंसी समान है?
Object.is(temp1[1], temp2[1]); // क्या दूसरी डिपेंडेंसी समान है?
Object.is(temp1[2], temp2[2]); // ...और इसी तरह हर डिपेंडेंसी के लिए...
जब आपको पता चल जाए कि कौन-सी डिपेंडेंसी मेमोइज़ेशन तोड़ रही है, तो उसे हटाने का तरीका ढूंढें या उसे भी मेमोइज़ करें।
मुझे लिस्ट में हर आइटम के लिए useCallback
कॉल करना है, लेकिन यह अनुमति नहीं है
मान लें Chart
कौम्पोनॅन्ट को memo
में रैप किया गया है। आप चाहते हैं कि ReportList
कौम्पोनॅन्ट के री-रेंडर होने पर लिस्ट में हर Chart
का री-रेंडर स्किप हो जाए। लेकिन आप लूप के अंदर useCallback
कॉल नहीं कर सकते:
function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 लूप के अंदर useCallback कॉल नहीं कर सकते:
const handleClick = useCallback(() => {
sendReport(item)
}, [item]);
return (
<figure key={item.id}>
<Chart onClick={handleClick} />
</figure>
);
})}
</article>
);
}
इसके बजाय, विशिष्ट आइटम के लिए अलग कौम्पोनॅन्ट बनाएँ और उसमें useCallback
रखें:
function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}
function Report({ item }) {
// ✅ टॉप लेवल पर useCallback कॉल करें:
const handleClick = useCallback(() => {
sendReport(item)
}, [item]);
return (
<figure>
<Chart onClick={handleClick} />
</figure>
);
}
या फिर, आप useCallback
हटा सकते हैं और Report
कौम्पोनॅन्ट को memo
में रैप कर सकते हैं। यदि item
प्रॉप नहीं बदलता, तो Report
री-रेंडर स्किप करेगा, जिससे Chart
भी री-रेंडर स्किप कर देगा:
function ReportList({ items }) {
// ...
}
const Report = memo(function Report({ item }) {
function handleClick() {
sendReport(item);
}
return (
<figure>
<Chart onClick={handleClick} />
</figure>
);
});