ज्या घटकांना (components) त्याची गरज नाही, अशा अनेक थरांच्या मधून props पास करणे हे खूप कंटाळवाणे काम आहे. एका दिवशी तुम्ही एखादे फीचर लॉन्च करत असता आणि दुसऱ्या दिवशी फक्त एक prop नाव बदलण्यासाठी तुम्हाला सहा फाईल्स एडिट कराव्या लागतात. थोडक्यात सांगायचे तर, हेच 'Prop Drilling' आहे: एका पैरेंटकडे (parent) डेटा आहे, एका खोलवर असलेल्या चाइल्डला (child) तो हवा आहे, आणि त्यांच्यामधील प्रत्येक घटक केवळ एक वाहक (courier) बनतो. ॲप अजूनही चालते, पण कोडबेस ठिसूळ (brittle) बनतो. एखादी मधली लेयर काढून टाका, आणि अर्धा ट्री कोसळतो. एखादा टाइप (type) बदला, आणि TypeScript तीन वेगवेगळ्या डिरेक्टरीजमध्ये तक्रार करू लागते. React Context API हे सर्व मध्यस्थ पूर्णपणे काढून टाकण्यासाठी अस्तित्वात आहे.

Prop Drilling प्रत्यक्षात कसे दिसते

एका सामान्य ॲप शेलची कल्पना करा. तुमच्याकडे एक App घटक आहे जो सध्याचा युजर मिळवतो (fetch करतो). App च्या आत Layout आहे, Layout च्या आत Sidebar आहे, Sidebar च्या आत Navigation आहे आणि शेवटी Navigation च्या आत UserAvatar आहे ज्याला प्रत्यक्षात युजर ऑब्जेक्टची गरज आहे.

तुमचा कोड असा दिसतो:

function App() {
  const user = { name: 'Aarav', role: 'admin' };
  return <Layout user={user} />;
}

function Layout({ user }) {
  return <Sidebar user={user} />;
}

function Sidebar({ user }) {
  return <Navigation user={user} />;
}

function Navigation({ user }) {
  return <UserAvatar user={user} />;
}

Layout, Sidebar, आणि Navigation त्या युजर ऑब्जेक्टचा वापर केवळ तो पुढे पाठवण्यासाठी करतात. ते स्वतःच्या नसलेले props साठवत राहतात, त्यांचे इंटरफेस फुगतात आणि त्यांची टेस्टिंग करण्यासाठी असा डेटा मॉक (mock) करावा लागतो ज्याचा त्यांना कधीही संबंध नसतो. खरी समस्या ही आहे की हे किती वेगाने पसरते. एक isLoggedIn फ्लॅग, locale स्ट्रिंग किंवा theme व्हॅल्यू जोडा, आणि हीच प्रक्रिया पुन्हा पुन्हा घडते.

Context API गेम कसा बदलतो

Context API ला एका वाय-फाय राउटरसारखे समजा. प्रत्येक खोलीत प्रत्येक उपकरणापर्यंत पोहोचण्यासाठी लांब केबल्स टाकण्याऐवजी, राउटर हवेतून सिग्नल पाठवतो. रेंजमध्ये असलेले कोणतेही उपकरण थेट कनेक्ट होऊ शकते. React च्या संदर्भात, राउटर म्हणजे Provider, सिग्नल म्हणजे तुमचा state किंवा डेटा, आणि उपकरण म्हणजे useContext कॉल करणारा कोणताही नेस्टेड घटक.

या सेटअपमध्ये तीन महत्त्वाचे भाग आहेत:

  • React.createContext() डेटा चॅनेल तयार करते.
  • Provider तुमच्या ट्रीचा एक भाग वेढून (wrap करून) घेतो आणि व्हॅल्यू प्रसारित करतो.
  • useContext हुकमुळे मध्यवर्ती props ला स्पर्श न करता वंशज (descendants) ती व्हॅल्यू प्राप्त करू शकतात.

तुमच्याकडे अजूनही एक ट्री आहे, पण रूट आणि लीफ (leaf) मधील फांद्यांना आता 'कुरिअर करारावर' सहमत होण्याची गरज नाही.

शून्यापासून Context तयार करणे

चला थीम सेटिंग्ससह एक ठोस उदाहरण तयार करूया, कारण बहुतेक ॲप्सना कधी ना कधी लाईट किंवा डार्क मोडची गरज असते.

प्रथम, context ऑब्जेक्ट तयार करा. हा एक पाईप आहे:

import { createContext, useState, useMemo } from 'react';

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export default ThemeContext;

त्यानंतर तुमच्या ॲप्लिकेशनला provider मध्ये वेढून घ्या. सहसा हे रूटच्या (root) जवळ घडते:

import { ThemeProvider } from './ThemeContext';

function App() {
  return (
    <ThemeProvider>
      <Layout />
    </ThemeProvider>
  );
}

आता कोणताही वंशज थेट सिग्नल मिळवू शकतो. येथे UI मध्ये खोलवर असलेला एक टॉगल बटन आहे:

import { useContext } from 'react';
import ThemeContext from './ThemeContext';

function ThemeToggle() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={() => setTheme(prev => prev === 'light' ? 'dark' : 'light')}
    >
      Current theme: {theme}
    </button>
  );
}

लक्षात घ्या की Layout, Sidebar, आणि Navigation यांना कधीही theme prop दिसत नाही. ते सामान्यपणे रेंडर होतात आणि ThemeToggle थेट context मधून त्याला हवी ती गोष्ट घेते. हे वायरिंग बाहेरून अदृश्य आहे, आणि नेमकी हीच मुख्य गोष्ट आहे.

Context प्रत्यक्षात कुठे वापरावा

Context अशा डेटासाठी सर्वोत्तम काम करते जो अनेक दूरवरचे घटक शेअर करतात परंतु कोणताही एक पैरेंट त्याचा मालक नसतो. योग्य पर्याय खालीलप्रमाणे आहेत:

  • Theme settings: लाईट किंवा डार्क मोड, ॲक्सेंट कलर्स किंवा फॉन्ट स्केलिंग सारख्या थीम सेटिंग्स.
  • Authentication state: सध्याचा युजर ऑब्जेक्ट, लॉगिन स्टेटस किंवा सेशन एक्स्पायरी सारखी ऑथेंटिकेशन स्टेट.
  • Language and locale: आंतरराष्ट्रीयीकरणासाठी (internationalization) भाषा आणि लोकेल सेटिंग्स.
  • Shopping cart data: शॉपिंग कार्ट डेटा जो हेडर बॅज, मिनी-कार्ट ड्रॉपडाउन आणि चेकआउट पेजवर सिंक असणे आवश्यक आहे.

प्रत्येक स्थानिक स्टेट (local state) Context मध्ये टाकण्याची इच्छा रोखा. दोन लेव्हल खाली असलेल्या फॉर्म इनपुटला ग्लोबल ब्रॉडकास्टची गरज नसते. खऱ्या क्रॉस-कटिंग गरजांसाठी Context वापरा आणि बाकी गोष्टी सामान्य props म्हणून ठेवा.

परफॉर्मन्सचे अडथळे आणि ते कसे टाळावेत

Context मोफत नाही. जेव्हा एखादी context व्हॅल्यू अपडेट होते, तेव्हा त्या context शी जोडलेला प्रत्येक घटक री-रेंडर होतो, जरी त्यांना हवी असलेली व्हॅल्यू बदलली नसेल तरीही. प्रत्येक पैरेंट रेंडरवर Provider मध्ये नवीन ऑब्जेक्ट लिटरल (object literal) टाकणे ही एक सामान्य चूक आहे.

आपल्या थीमच्या उदाहरणात, जेव्हा ThemeProvider त्याच्या स्वतःच्या पैरेंटमुळे री-रेंडर होतो, तेव्हा { theme, setTheme } ही एक्सप्रेशन एक नवीन ऑब्जेक्ट तयार करते. React ला एक नवीन रेफरन्स दिसतो आणि प्रत्येक कंज्युमर अपडेट होतो. जर तुमची थीम क्वचितच बदलत असेल पण तुमच्या ॲपची स्टेट वारंवार बदलत असेल, तर तुम्ही अशा री-रेंडर्ससाठी परफॉर्मन्स खर्च करत आहात ज्यांची तुम्हाला गरज नाही.

यावर उपाय दोन प्रकारे आहे.

तुमचे contexts अपडेटच्या वारंवारतेनुसार (frequency) विभाजित करा. एक UserContext जो लॉगिनच्या वेळी एकदाच बदलतो, तो NotificationContext सोबत शेअर केला जाऊ नये जो दर काही सेकंदांनी अपडेट होतो. त्यांना वेगळे ठेवा जेणेकरून स्थिर डेटा (static data) अस्थिर डेटासोबत (volatile data) री-रेंडरच्या प्रवाहात येणार नाही.

जेव्हा व्हॅल्यू एखादा ऑब्जेक्ट किंवा ॲरे असेल, तेव्हा useMemo मध्ये ती वेढून घ्या. React ला एक स्थिर रेफरन्स द्या:

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const value = useMemo(() => ({ theme, setTheme }), [theme]);

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

आता ऑब्जेक्टची ओळख (object identity) तेव्हाच बदलते जेव्हा theme खरोखर बदलतो. जे descendant components कॉन्टेक्स्टवर अवलंबून आहेत परंतु पुढे React.memo द्वारे संरक्षित आहेत, ते पुढील प्रक्रिया टाळतील.

तुमचा वेळ वाया घालवणाऱ्या चुका

प्रोडक्शन कोडमध्ये अजूनही दिसणाऱ्या दोन चुका टाळणे सोपे आहे.

पहिले म्हणजे, स्वतः कॉन्टेक्स्ट एक्सपोर्ट करायला विसरणे. जर तुम्ही फक्त Provider wrapper एक्सपोर्ट केला आणि context object खाजगी (private) ठेवला, तर नवीन फीचर लिहिणारा डेव्हलपर तुमच्या मॉड्यूलमध्ये बदल (refactoring) केल्याशिवाय useContext कॉल करू शकणार नाही. कॉन्टेक्स्ट एक्सपोर्ट करा जेणेकरून वापरकर्ते (consumers) provider आणि consumer hook दोन्ही स्वच्छपणे इम्पोर्ट करू शकतील.

दुसरे म्हणजे, संबंधित Provider च्या बाहेर useContext कॉल करणे. जर ThemeToggle अशा ट्रीच्या शाखेत (branch) रेंडर होत असेल जी ThemeProvider मध्ये गुंडाळलेली (wrapped) नाही, तर हुक createContext ला दिलेली डिफॉल्ट व्हॅल्यू परत करतो, किंवा जर तुम्ही काहीही दिले नसेल तर undefined परत करतो. यामुळे cannot read property of undefined सारख्या 'silent failures' ला निमंत्रण मिळते. तुम्ही एक योग्य डिफॉल्ट व्हॅल्यू देऊन किंवा हुक कॉलच्या सुरुवातीलाच स्पष्ट एरर देऊन यापासून संरक्षण करू शकता.

Context विरुद्ध Redux: साधेपणा राखा

तुम्हाला नेहमीच Redux ची गरज नसते. मध्यम आकाराच्या प्रोजेक्ट्ससाठी, useState किंवा useReducer सोबत वापरलेला Context स्टेट शेअरिंगचे बहुतेक काम पूर्ण करतो. Redux तेव्हा प्रभावी ठरते जेव्हा तुम्हाला time-travel debugging, जटिल middleware, किंवा क्रमाने रोलबॅक कराव्या लागणाऱ्या ग्लोबल ट्रान्झॅक्शन्सची गरज असते. जर तुमची संपूर्ण स्टेट फक्त एक user object, एक theme string आणि एक cart array असेल, तर एखादी store library अनावश्यक boilerplate वाढवते ज्याचा तुम्हाला कधीही उपयोग होणार नाही.

असे असले तरी, Context स्वतःहून पूर्ण स्टेट मॅनेजमेंट सिस्टम नाही. ते तुम्हाला एक सिंगल ग्लोबल स्नॅपशॉट देत नाही आणि असंबंधित कॉन्टेक्स्टमधील अपडेट्स एकत्रित (batch) करत नाही. त्याचा वापर prop drilling च्या पर्यायासाठी करा, तुमच्या संपूर्ण डेटा लेयरसाठी ऑपरेटिंग सिस्टम म्हणून नाही.

मुख्य निष्कर्ष

ज्या कंपोनेंट्सना त्यांची गरज नाही, त्यांच्यामधून props पास करणे थांबवा. तुमच्या ट्रीमध्ये पसरलेल्या डेटासाठी विशिष्ट (focused) कॉन्टेक्स्ट तयार करा, consumers कव्हर करण्यासाठी providers पुरेशा उंचीवर (high enough) वापरा, आणि जेव्हा तुम्ही collections किंवा functions पास करत असाल तेव्हा नेहमी value object स्थिर (stabilize) ठेवा. Context API तुमचा React कोड थेट ठेवते: props स्थानिक राहतात, ग्लोबल डेटा विनासायास प्रवास करतो आणि तुमच्या कंपोनंटच्या सीमा (boundaries) स्वच्छ राहतात.