हर React डेवलपर अंततः एक ही समस्या का सामना करता है। आप अपने टॉप-लेवल App कंपोनेंट के अंदर एक user object fetch करते हैं। फिर आप उसे नीचे पास करते हैं। और फिर से नीचे। एक route wrapper, एक layout shell, और एक sidebar container के माध्यम से, सिर्फ इसलिए ताकि तीन लेयर्स गहरे एक छोटे से avatar component में प्रोफाइल पिक्चर दिखाई दे सके। बीच के कंपोनेंट्स को उस user object से कोई लेना-देना नहीं होता। वे बस एक पार्सल को आगे बढ़ा रहे होते हैं। इसे ही prop drilling कहते हैं, और यह एक साफ-सुथरे component tree को एक निराशाजनक 'game of telephone' में बदल देता है।
असली परेशानी तब शुरू होती है जब उस डेटा का स्ट्रक्चर बदल जाता है। हो सकता है कि backend user.avatar के बजाय user.profile.avatar को नेस्ट करना शुरू कर दे। अचानक, आप उन पांच फाइलों में TypeScript interfaces या PropTypes को एडिट करने लगते हैं जो खुद उस डेटा का उपयोग कभी नहीं करतीं। यहीं पर React Context API काम आता है।
Context डेटा फ्लो को कैसे बदलता है
Context को अपने घर के केंद्र में रखे एक WiFi राउटर की तरह समझें। इसके बिना, अपने लैपटॉप तक सिग्नल पहुँचाने के लिए आपको हर कमरे में Ethernet केबल बिछानी पड़ेगी। इसके साथ, राउटर हवा के माध्यम से सिग्नल प्रसारित (broadcast) करता है, और सही पासवर्ड वाला कोई भी डिवाइस सीधे कनेक्ट हो सकता है। दीवारें मायने नहीं रखतीं।
React के संदर्भ में, आपके ऐप का root हर लेयर को कूरियर की तरह काम करने के लिए कहे बिना, component tree के माध्यम से डेटा प्रसारित कर सकता है। कोई भी nested component उस ब्रॉडकास्ट को सब्सक्राइब कर सकता है और ठीक वही प्राप्त कर सकता है जिसकी उसे आवश्यकता है।
तीन मुख्य हिस्से
Context API तीन मुख्य हिस्सों से मिलकर बना है।
React.createContext() ब्रॉडकास्ट चैनल सेटअप करता है। यह एक object लौटाता है जिसमें एक Provider और (पुराने कोड में) एक Consumer होता है। आपको किसी विशेष फीचर के लिए इसे केवल एक बार कॉल करने की आवश्यकता होती है।
The Provider एक कंपोनेंट है जो आपके tree के एक हिस्से को रैप (wrap) करता है। यह value नामक एक prop स्वीकार करता है। आप उस prop में जो कुछ भी रखते हैं, वह हर descendant के लिए उपलब्ध हो जाता है, चाहे वे कितनी भी गहराई में क्यों न हों।
useContext वह Hook है जो एक function component को उस ब्रॉडकास्ट से जुड़ने की अनुमति देता है। अपने कंपोनेंट के अंदर, आप अपने द्वारा बनाए गए context object को useContext में पास करते हैं, और यह वर्तमान value लौटाता है। बस इतना ही। कोई wrappers नहीं, कोई extra props नहीं।
Hooks आने से पहले, आपको render props के साथ Consumer pattern का उपयोग करना पड़ता था। यह काम तो करता था, लेकिन इससे बहुत अधिक indentation और wrapper का कचरा (clutter) हो जाता था। useContext ने इन सबको आपके function body के अंदर एक ही लाइन में समेट दिया।
Context का उपयोग वास्तव में कब करना चाहिए
सिर्फ आदत के कारण Context का उपयोग न करें। इसे उस डेटा के लिए बनाया गया है जिसे आपके tree की विभिन्न शाखाओं (branches) में कई असंबंधित कंपोनेंट्स साझा करते हैं। इसके अच्छे उदाहरणों में शामिल हैं:
- Theme settings. न केवल light या dark mode, बल्कि spacing tokens, color palettes, और font scales भी। हर styled button और modal के माध्यम से इन्हें मैन्युअल रूप से पास करना बहुत थकाऊ हो जाता है।
- User authentication. Login status, permissions array, या current user object। आपका header bar, एक dashboard widget, और एक private route guard, ये सभी tree के अलग-अलग कोनों में हो सकते हैं।
- Language preferences. Locale strings, date formats, और currency symbols। form labels जैसे गहरे leaf components को इनकी आवश्यकता होती है, बिना रास्ते में आने वाले हर parent को इनके बारे में जाने।
- Shopping cart data. Item count, total value, और add-to-cart functions। header badge और checkout page को एक ही state की आवश्यकता होती है, लेकिन वे आमतौर पर पूरी तरह से अलग layout branches के अंतर्गत होते हैं।
एक व्यावहारिक Theme Switcher
Context को काम करते हुए देखने का सबसे स्पष्ट तरीका theme toggle है। यहाँ बताया गया है कि आप उन विवरणों को छोड़े बिना इसे कैसे सेटअप कर सकते हैं जो वास्तव में मायने रखते हैं।
सबसे पहले, एक ThemeContext.js फ़ाइल बनाएँ। React.createContext() को कॉल करें और परिणाम को स्टोर करें। फिर एक ThemeProvider कंपोनेंट बनाएँ जो useState या useReducer के साथ वर्तमान theme को मैनेज करे। बच्चों (children) को अपने context के Provider में रैप करें, और एक ऐसा object पास करें जिसमें वर्तमान theme और उसे toggle करने वाला function दोनों हों। ThemeProvider और context object दोनों को export करें।
दूसरा, अपने app entry point पर जाएँ। ThemeProvider को import करें और अपने पूरे application को इसके साथ रैप करें। यदि आप इस चरण को छोड़ देते हैं, तो बाद में context को पढ़ने की कोशिश करने वाली किसी भी चीज़ को केवल default value ही दिखाई देगी।
तीसरा, एक Header या Content कंपोनेंट के अंदर, context object और useContext को import करें। Hook को कॉल करें, theme और toggle function को destructure करें, और अपनी CSS classes को conditionally लागू करें। एक बटन जोड़ें जो toggle को कॉल करे। कंपोनेंट को अपने parent से कभी भी theme prop प्राप्त नहीं होता है। यह सीधे हवा से सिग्नल खींच लेता है।
Prop Drilling, Context, या Redux?
इन टूल्स के बीच चुनाव करना वफादारी के बारे में कम और आपके स्टेट (state) के स्वरूप के बारे में अधिक है।
Prop drilling दो या तीन स्तरों की गहराई के लिए बिल्कुल ठीक है। यह स्पष्ट है, आपके IDE में इसे ट्रैक करना आसान है, और डिपेंडेंसीज़ (dependencies) को स्पष्ट रखता है। समस्याएँ तभी आती हैं जब आप एक ही प्रॉप को छह या सात परतों के माध्यम से गुजारना शुरू करते हैं।
Context API React के साथ ही आता है। इसका मतलब है कि कोई अतिरिक्त बंडल साइज़ नहीं और कोई बाहरी सेटअप नहीं। यह छोटे से मध्यम ग्लोबल स्टेट को बहुत अच्छे से संभालता है, विशेष रूप से वह डेटा जो कम ही बदलता है जैसे कि थीम्स (themes) या यूजर प्रोफाइल।
Redux के लिए अतिरिक्त लाइब्रेरीज़ इंस्टॉल करने और बॉयलरप्लेट (boilerplate) लिखने की आवश्यकता होती है। यह तब फायदेमंद होता है जब आपका स्टेट लॉजिक जटिल हो, जब स्टेट के कई स्लाइस (slices) गहराई से एक-दूसरे के साथ इंटरैक्ट करते हों, या जब आपको टाइम-ट्रैवल डिबगिंग और मिडलवेयर की आवश्यकता हो। साधारण ग्लोबल डेटा के लिए, Redux ज़रूरत से ज़्यादा (overkill) है।
वह परफॉरमेंस हकीकत जिसके बारे में कोई बात नहीं करता
यहाँ वह पेच है जो जूनियर इम्प्लीमेंटेशन को सीनियर से अलग करता है। जब Context Provider की वैल्यू बदलती है, तो उस कॉन्टेक्स्ट का उपयोग करने वाला प्रत्येक कंपोनेंट फिर से रेंडर (re-render) होता है। इससे कोई फर्क नहीं पड़ता कि वह विशेष स्लाइस जिसके बारे में कंपोनेंट को परवाह है, वही रहा या नहीं। React नई रेफरेंस (reference) को देखता है और अपडेट शेड्यूल करता है।
यदि आप अपने पूरे एप्लिकेशन स्टेट को एक विशाल StoreContext में डाल देते हैं, तो आपने प्रभावी रूप से अपने पूरे UI को एक साथ चिपका दिया है। थीम सेटिंग बदलने से आपका शॉपिंग कार्ट, आपके डैशबोर्ड चार्ट और आपकी नोटिफिकेशन लिस्ट फिर से रेंडर हो जाएगी। यह अनावश्यक काम है।
अपने कॉन्टेक्स्ट को डोमेन के आधार पर विभाजित करें। विजुअल सेटिंग्स के लिए ThemeContext, प्रोफाइल डेटा के लिए UserContext, और कॉमर्स स्टेट के लिए CartContext रखें। यदि कोई यूजर अपना डिस्प्ले नाम बदलता है, तो आपका हेडर प्रोडक्ट ग्रिड को छुए बिना अपडेट हो जाएगा। साथ ही, इस बात का ध्यान रखें कि आप Provider के value प्रॉप में क्या पास कर रहे हैं। यदि आप रेंडर के दौरान इनलाइन { theme, toggleTheme } जैसा ऑब्जेक्ट लिटरल पास करते हैं, तो आप हर रेंडर पर एक नई रेफरेंस बनाते हैं और अनावश्यक अपडेट ट्रिगर करते हैं। यदि वैल्यू में फंक्शन या नॉन-प्रिमिटिव डेटा है, तो useMemo के साथ उस स्ट्रक्चर को स्थिर (stabilize) करें।
वे गलतियाँ जो घंटों बर्बाद कर देती हैं
दो गलतियाँ टीमों को बार-बार पकड़ लेती हैं।
कॉन्टेक्स्ट ऑब्जेक्ट को एक्सपोर्ट करना भूल जाना। ThemeProvider कंपोनेंट को एक्सपोर्ट करना और फिर useContext(ThemeProvider) को कॉल करने की कोशिश करना आसान है। यह ऐसे काम नहीं करता है। हुक (Hook) को createContext द्वारा लौटाए गए कॉन्टेक्स्ट ऑब्जेक्ट की आवश्यकता होती है, न कि रैपर कंपोनेंट की। यदि आप केवल Provider को एक्सपोर्ट करते हैं, तो आपके कंज्यूमर्स के पास इम्पोर्ट करने के लिए कुछ नहीं होगा।
अपने Provider के बाहर useContext को कॉल करना। हुक वह डिफॉल्ट वैल्यू लौटाता है जो आपने createContext में पास की थी। यदि आपने डिफॉल्ट पास नहीं किया है, तो आपको undefined मिलेगा। यदि आपका कंपोनेंट ट्री कंज्यूमर को Provider की तुलना में DOM में ऊपर रेंडर करता है, या यदि Provider पूरी तरह से गायब है, तो आपका डेटा बस नहीं पहुंचेगा। दोबारा जांच लें कि आपकी index या root फ़ाइल वास्तव में ऐप को रैप (wrap) करती है या नहीं।
असली निष्कर्ष
React Context स्टेट मैनेजमेंट की कोई क्रांति नहीं है। यह एक विशिष्ट स्थानिक समस्या (spatial problem) के लिए एक लक्षित टूल है: हर लेयर को पोस्ट ऑफिस बनाए बिना डेटा को दूर के कंपोनेंट्स तक पहुँचाना। इसका उपयोग वास्तव में ग्लोबल डेटा के लिए करें, रेंडरिंग परफॉरमेंस को सुरक्षित रखने के लिए अपने कॉन्टेक्स्ट को डोमेन के आधार पर विभाजित रखें, और सिग्नल पढ़ने की कोशिश करने से पहले हमेशा अपने ट्री को सही Provider के साथ रैप करें। इन आदतों को अपनाएं, और आपके कंपोनेंट ट्री साफ, तेज़ और समझने में आसान रहेंगे।
