అవసరం లేని కాంపోనెంట్స్ ద్వారా ప్రాప్స్ను పంపడం చాలా కష్టమైన పని. ఒకరోజు మీరు ఒక ఫీచర్ను విడుదల చేస్తుంటే, మరుసటి రోజు ఒకే ఒక ప్రాప్ను రీనేమ్ చేయడానికి ఆరు ఫైళ్లను ఎడిట్ చేయాల్సి రావచ్చు. ఇదే ప్రాప్ డ్రిల్లింగ్ (prop drilling) యొక్క సారాంశం: ఒక పేరెంట్ వద్ద డేటా ఉంటుంది, ఒక లోతైన చైల్డ్ కాంపోనెంట్కు అది అవసరమవుతుంది, మరియు వాటి మధ్యలో ఉన్న ప్రతి కాంపోనెంట్ ఒక కొరియర్ లాగా మారుతుంది. యాప్ నడుస్తుంది కానీ, కోడ్బేస్ చాలా సున్నితంగా (brittle) మారుతుంది. మధ్యలో ఉన్న ఒక లేయర్ను తొలగిస్తే, సగం ట్రీ కూలిపోతుంది. ఒక టైప్ను మారిస్తే, మూడు డైరెక్టరీలలో TypeScript ఎర్రర్లను చూపిస్తుంది. ఆ మధ్యవర్తులను పూర్తిగా తొలగించడానికి React Context API ఉపయోగపడుతుంది.
ప్రాప్ డ్రిల్లింగ్ నిజంగా ఎలా ఉంటుందో చూడండి
ఒక సాధారణ యాప్ షెల్ను ఊహించుకోండి. మీ వద్ద ప్రస్తుత యూజర్ను ఫెచ్ చేసే ఒక App కాంపోనెంట్ ఉంది. 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 ఆ యూజర్ ఆబ్జెక్ట్తో ఏమీ చేయవు, కేవలం దానిని కిందకు పంపిస్తాయి. అవి తమకు అవసరం లేని ప్రాప్స్ను పేరుకుపోతాయి, వాటి ఇంటర్ఫేస్లు పెరుగుతాయి, మరియు వాటిని టెస్ట్ చేయడానికి ఎప్పుడూ వాడని డేటాను మాక్ (mocking) చేయాల్సి వస్తుంది. అసలైన సమస్య ఏమిటంటే ఇది ఎంత వేగంగా వ్యాపిస్తుందనేది. ఒక isLoggedIn ఫ్లాగ్, ఒక locale స్ట్రింగ్, లేదా ఒక theme వాల్యూని జోడిస్తే, ఇదే ప్రక్రియ మళ్ళీ మళ్ళీ జరుగుతుంది.
Context API ఆటను ఎలా మారుస్తుంది
Context APIని ఒక WiFi రూటర్ లాగా అనుకోండి. ప్రతి పరికరానికి చేరుకోవడానికి ప్రతి గది గుండా పొడవైన కేబుల్స్ వేయడానికి బదులుగా, రూటర్ గాలి ద్వారా సిగ్నల్ను పంపుతుంది. పరిధిలో ఉన్న ఏ పరికరమైనా నేరుగా కనెక్ట్ కావచ్చు. React పరంగా చెప్పాలంటే, రూటర్ అనేది Provider, సిగ్నల్ అనేది మీ state లేదా data, మరియు పరికరం అనేది useContextని పిలిచే ఏదైనా నెస్టెడ్ కాంపోనెంట్.
దీని సెటప్లో మూడు ముఖ్యమైన భాగాలు ఉన్నాయి:
React.createContext()డేటా ఛానెల్ను నిర్మిస్తుంది.- Provider మీ ట్రీ యొక్క ఒక భాగాన్ని చుట్టి (wrap) ఒక విలువను ప్రసారం చేస్తుంది.
useContextహుక్ ద్వారా మధ్యంతర ప్రాప్స్ను తాకకుండానే సంతతి కాంపోనెంట్స్ ఆ విలువను పొందవచ్చు.
మీకు ఇంకా ట్రీ ఉంటుంది, కానీ రూట్ మరియు లీఫ్ మధ్య ఉన్న బ్రాంచ్లు ఇకపై కొరియర్ ఒప్పందం (courier contract) గురించి ఆలోచించాల్సిన అవసరం లేదు.
మొదటి నుండి ఒక 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తో చుట్టండి. సాధారణంగా ఇది రూట్ దగ్గర జరుగుతుంది:
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 ప్రాప్ను చూడవని గమనించండి. అవి సాధారణంగా రెండర్ అవుతాయి, మరియు ThemeToggle దానికి కావలసిన దానిని నేరుగా context నుండి తీసుకుంటుంది. బయట నుండి చూస్తే ఈ కనెక్షన్ కనిపించదు, అదే దీని ముఖ్య ఉద్దేశ్యం.
Context నిజంగా ఎక్కడ ఉపయోగించాలి
చాలా దూరంగా ఉన్న కాంపోనెంట్స్ పంచుకునే డేటా కోసం Context బాగా పనిచేస్తుంది, కానీ ఆ డేటా ఏ ఒక్క పేరెంట్ వద్దే ఉండదు. మంచి ఉదాహరణలు:
- Theme settings లైట్ లేదా డార్క్ మోడ్, యాసట్ కలర్స్ (accent colors), లేదా ఫాంట్ స్కేలింగ్ వంటివి.
- Authentication state ప్రస్తుత యూజర్ ఆబ్జెక్ట్, లాగిన్ స్టేటస్, లేదా సెషన్ ఎక్స్పైరీ వంటివి.
- Language and locale ఇంటర్నేషనలైజేషన్ కోసం లాంగ్వేజ్ మరియు లోకేల్ సెట్టింగ్స్.
- Shopping cart data హెడర్ బ్యాడ్జ్, మినీ-కార్ట్ డ్రాప్డౌన్ మరియు చెక్అవుట్ పేజీల మధ్య సింక్ అయి ఉండాల్సిన షాపింగ్ కార్ట్ డేటా.
ప్రతి లోకల్ స్టేట్ను Contextలోకి పంపే ప్రయత్నం చేయకండి. రెండు స్థాయిల కింద ఉన్న ఫామ్ ఇన్పుట్కు గ్లోబల్ బ్రాడ్కాస్ట్ అవసరం లేదు. నిజమైన క్రాస్-కట్టింగ్ కన్సర్న్స్ (cross-cutting concerns) కోసం మాత్రమే Contextను ఉపయోగించండి, మిగిలిన వాటిని సాధారణ ప్రాప్స్లాగే ఉంచండి.
పెర్ఫార్మెన్స్ ట్రాప్స్ మరియు వాటిని ఎలా నివారించాలి
Context వాడటం వల్ల ఖర్చు (cost) ఉంటుంది. ఒక context విలువ అప్డేట్ అయినప్పుడు, ఆ contextతో అనుసంధానించబడిన ప్రతి కాంపోనెంట్ రీ-రెండర్ అవుతుంది, వారు ఆశించే విలువ మారకపోయినా కూడా. ప్రతి పేరెంట్ రెండర్ అయినప్పుడు Providerలోకి కొత్త ఆబ్జెక్ట్ లిటరల్ను పంపడం అనేది ఒక సాధారణ తప్పు.
మన థీమ్ ఉదాహరణలో, ThemeProvider యొక్క పేరెంట్ అప్డేట్ అయిన ప్రతిసారీ, { theme, setTheme } అనే ఎక్స్ప్రెషన్ ఒక కొత్త ఆబ్జెక్ట్ను సృష్టిస్తుంది. React ఒక కొత్త రిఫరెన్స్ను చూస్తుంది, మరియు ప్రతి కన్స్యూమర్ అప్డేట్ అవుతుంది. మీ థీమ్ అరుదుగా మారుతూ, మీ యాప్ స్టేట్ తరచుగా మారుతుంటే, మీకు అవసరం లేని రీ-రెండర్ల వల్ల పెర్ఫార్మెన్స్ తగ్గుతుంది.
దీనికి రెండు పరిష్కారాలు ఉన్నాయి.
మీ కాంటెక్ట్లను అప్డేట్ ఫ్రీక్వెన్సీని బట్టి విభజించండి. లాగిన్ అయినప్పుడు ఒకసారి మాత్రమే మారే UserContext, ప్రతి కొన్ని సెకన్లకు అప్డేట్ అయ్యే NotificationContextతో ఒకే ప్రొవైడర్ను పంచుకోకూడదు. స్టాటిక్ డేటా, వోలటైల్ డేటా (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>
);
}
ఇప్పుడు theme నిజంగా మారినప్పుడు మాత్రమే ఆబ్జెక్ట్ ఐడెంటిటీ మారుతుంది. కాంటెక్స్ట్ పట్ల ఆసక్తి ఉన్నప్పటికీ, కింద ఉన్న React.memo ద్వారా రక్షించబడిన డిసెండెంట్ కాంపోనెంట్స్ ఆ పనిని స్కిప్ చేస్తాయి.
మీ సమయాన్ని వృధా చేసే తప్పులు
ప్రొడక్షన్ కోడ్లో ఇప్పటికీ కనిపించే రెండు తప్పులను సులభంగా నివారించవచ్చు.
మొదటిది, కాంటెక్స్ట్ను ఎగుమతి (export) చేయడం మర్చిపోవడం. మీరు కేవలం Provider wrapperను మాత్రమే ఎగుమతి చేసి, context ఆబ్జెక్ట్ను ప్రైవేట్గా ఉంచితే, కొత్త ఫీచర్ను రాస్తున్న డెవలపర్ మీ మాడ్యూల్ను రీఫ్యాక్టర్ (refactoring) చేయకుండా useContextను పిలవలేరు. వినియోగదారులు (consumers) provider మరియు consumer hook రెండింటినీ సులభంగా ఇంపోర్ట్ చేసుకునేలా కాంటెక్స్ట్ను ఎగుమతి చేయండి.
రెండవది, సంబంధిత Provider వెలుపల useContextను పిలవడం. ఒకవేళ ThemeToggle, ThemeProviderలో చుట్టబడని (wrapped) ట్రీ యొక్క బ్రాంచ్లో రెండర్ అయితే, ఆ హుక్ createContextకు పంపిన డిఫాల్ట్ విలువను లేదా మీరు ఏమీ పంపకపోతే undefinedను తిరిగి ఇస్తుంది. ఇది cannot read property of undefined వంటి సైలెంట్ ఫెయిల్యూర్స్కు దారితీస్తుంది. ఒక సరైన డిఫాల్ట్ విలువను కేటాయించడం ద్వారా లేదా హుక్ కాల్ ప్రారంభంలోనే స్పష్టమైన ఎర్రర్ను చూపించడం ద్వారా మీరు దీనిని నివారించవచ్చు.
Context vs Redux: సరళంగా ఉంచండి
మీకు ఎల్లప్పుడూ Redux అవసరం లేదు. మధ్యస్థ పరిమాణ ప్రాజెక్టుల కోసం, useState లేదా useReducerతో కలిపి వాడే Context, స్టేట్ షేరింగ్ (state sharing)లో ఎక్కువ భాగాన్ని కవర్ చేస్తుంది. మీకు time-travel debugging, సంక్లిష్టమైన middleware, లేదా క్రమ పద్ధతిలో రోల్ బ్యాక్ చేయాల్సిన గ్లోబల్ ట్రాన్సాక్షన్స్ అవసరమైనప్పుడు Redux అద్భుతంగా పనిచేస్తుంది. మీ మొత్తం స్టేట్ కథ కేవలం ఒక user object, ఒక theme string మరియు ఒక cart array మాత్రమే అయితే, ఒక store library అనవసరమైన boilerplate కోడ్ను జోడిస్తుంది, దానిని మీరు ఎప్పటికీ ఉపయోగించలేరు.
అయినప్పటికీ, Context అనేది స్వయంగా పూర్తి స్థాయి స్టేట్ మేనేజ్మెంట్ సిస్టమ్ కాదు. ఇది మీకు ఒకే గ్లోబల్ స్నాప్షాట్ను ఇవ్వదు మరియు సంబంధం లేని కాంటెక్ట్ల మధ్య అప్డేట్లను బ్యాచ్ చేయదు. దీనిని prop drilling కి ప్రత్యామ్నాయంగా ఉపయోగించండి, మీ మొత్తం డేటా లేయర్ కోసం ఆపరేటింగ్ సిస్టమ్గా కాదు.
అసలైన సారాంశం
వాటితో సంబంధం లేని కాంపోనెంట్స్ ద్వారా propsలను పంపడం (threading) ఆపండి. మీ ట్రీ అంతటా విస్తరించే డేటా కోసం ప్రత్యేకమైన (focused) కాంటెక్ట్లను సృష్టించండి, కన్స్యూమర్లను కవర్ చేసేలా ప్రొవైడర్లను తగినంత ఎత్తులో (high enough) చుట్టండి, మరియు మీరు కలెక్షన్స్ లేదా ఫంక్షన్లను పంపేటప్పుడు ఎల్లప్పుడూ value objectను స్థిరీకరించండి (stabilize). Context API మీ React కోడ్ను సూటిగా ఉంచుతుంది: props లోకల్గా ఉంటాయి, గ్లోబల్ డేటా వైర్లెస్ (wireless)గా ప్రయాణిస్తుంది మరియు మీ కాంపోనెంట్ బౌండరీస్ క్లీన్గా ఉంటాయి.
