தொடர்பில்லாத கூறுகளின் (components) அடுக்குகளின் வழியாக props-களைக் கடத்துவது மிகவும் சலிப்பான வேலை. ஒரு நாள் நீங்கள் ஒரு புதிய அம்சத்தை (feature) வெளியிடுகிறீர்கள், அடுத்த நாள் ஒரு single prop-ஐ பெயர் மாற்றவே ஆறு கோப்புகளைத் திருத்த வேண்டியிருக்கிறது. இதுதான் சுருக்கமாகச் சொன்னால் prop drilling: ஒரு parent-இடம் தரவு (data) உள்ளது, ஒரு ஆழமான child-க்கு அது தேவைப்படுகிறது, மேலும் அவற்றுக்கு இடையில் உள்ள ஒவ்வொரு component-உம் ஒரு தூதுவராக (courier) மாறுகிறது. ஆப் இயங்கும், ஆனால் codebase பலவீனமாகிவிடும். ஒரு நடுப்பகுதியை நீக்கினால், பாதி tree-யும் சரிந்துவிடும். ஒரு type-ஐ மாற்றினால், மூன்று கோப்பகங்களில் (directories) TypeScript புகார் செய்யும். அந்தத் தூதுவர்களை முற்றிலும் நீக்குவதற்காகவே React Context API உருவாக்கப்பட்டது.
Prop Drilling உண்மையில் எப்படி இருக்கும்
ஒரு சாதாரண app shell-ஐ கற்பனை செய்து பாருங்கள். தற்போதைய பயனரைப் பெறும் (fetch) ஒரு App component உங்களிடம் உள்ளது. App-க்குள் ஒரு Layout உள்ளது, Layout-க்குள் ஒரு Sidebar உள்ளது, Sidebar-க்குள் ஒரு Navigation உள்ளது, இறுதியாக Navigation-க்குள் பயனர் பொருளை (user object) உண்மையில் தேவைப்படும் UserAvatar உள்ளது.
உங்கள் குறியீடு (code) இப்படித்தான் இருக்கும்:
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 ஆகிய கூறுகள் அந்த user object-ஐக் கீழே கடத்துவதைத் தவிர வேறு எதுவும் செய்வதில்லை. அவை தங்களுக்குச் சொந்தமில்லாத props-களைச் சேகரிக்கின்றன, அவற்றின் interfaces வீங்குகின்றன, மேலும் அவற்றைப் பரிசோதிப்பதற்கு (testing) அவை ஒருபோதும் தொடாத தரவுகளை mock செய்ய வேண்டியுள்ளது. இது எவ்வளவு வேகமாகப் பரவுகிறது என்பதே உண்மையான சிக்கல். ஒரு isLoggedIn flag, ஒரு locale string, அல்லது ஒரு theme value-ஐச் சேர்த்தால், அதே வரிசை மீண்டும் தொடரும்.
Context API விளையாட்டை எப்படி மாற்றுகிறது
Context API-ஐ ஒரு WiFi router போலக் கருதுங்கள். ஒவ்வொரு சாதனத்தையும் சென்றடைய ஒவ்வொரு அறையிலும் நீண்ட கேபிள்களைக் கொண்டு செல்வதற்குப் பதிலாக, router காற்றில் ஒரு சிக்னலை அனுப்புகிறது. வரம்பிற்குள் (range) இருக்கும் எந்தச் சாதனமும் நேரடியாகத் தொடர்பு கொள்ளலாம். React மொழியில் சொன்னால், router என்பது Provider, சிக்னல் என்பது உங்கள் state அல்லது data, மற்றும் சாதனம் என்பது useContext-ஐ அழைக்கும் எந்தவொரு nested component-உம் ஆகும்.
இந்த அமைப்பில் மூன்று முக்கியப் பகுதிகள் உள்ளன:
React.createContext()தரவுச் சேனலை (data channel) உருவாக்குகிறது.- Provider உங்கள் tree-யின் ஒரு பகுதியைச் சூழ்ந்து கொண்டு ஒரு மதிப்பை (value) கடத்துகிறது.
useContexthook, இடைநிலை props-களைத் தொடாமலேயே அதன் கீழ் உள்ள கூறுகள் (descendants) அந்த மதிப்பைப் பெற அனுமதிக்கிறது.
உங்களிடம் இன்னும் ஒரு tree உள்ளது, ஆனால் root மற்றும் leaf ஆகியவற்றுக்கு இடையேயான கிளைகள் இனி ஒரு தூதுவர் ஒப்பந்தத்தில் (courier contract) உடன்பட வேண்டிய அவசியமில்லை.
ஒரு Context-ஐ ஆரம்பத்திலிருந்து உருவாக்குதல்
தீம் அமைப்புகளைக் (theme settings) கொண்டு ஒரு நடைமுறை உதாரணத்தை உருவாக்குவோம், ஏனெனில் பெரும்பாலான ஆப்ஸிற்கு ஏதோ ஒரு கட்டத்தில் light அல்லது dark mode தேவைப்படும்.
முதலில், context பொருளை உருவாக்கவும். இதுதான் குழாய் (pipe):
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;
பின்னர் உங்கள் பயன்பாட்டை (application) provider-க்குள் சுற்றவும். பொதுவாக இது root-க்கு அருகில் நடக்கும்:
import { ThemeProvider } from './ThemeContext';
function App() {
return (
<ThemeProvider>
<Layout />
</ThemeProvider>
);
}
இப்போது எந்தவொரு descendant-உம் சிக்னலை நேரடியாகப் பெற முடியும். இதோ UI-க்குள் ஆழமாகப் புதைந்துள்ள ஒரு toggle button:
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-ஐ ஒருபோதும் பார்ப்பதில்லை என்பதைக் கவனியுங்கள். அவை இயல்பாகவே render ஆகின்றன, மேலும் ThemeToggle தனக்குத் தேவையானதை நேரடியாக context-லிருந்து எடுத்துக் கொள்கிறது. இதன் இணைப்பு (wiring) வெளியிலிருந்து தெரியாது, அதுதான் இதன் முக்கிய நோக்கமும் கூட.
Context உண்மையில் எங்கு பொருந்தும்
பல தொலைதூரக் கூறுகள் (distant components) பகிர்ந்து கொள்ளும், ஆனால் எந்தவொரு தனி parent-உம் அதைத் தெளிவாகத் தன் வசம் வைத்திருக்காத தரவுகளுக்கு Context சிறப்பாகச் செயல்படும். சிறந்த உதாரணங்கள்:
- Theme settings (எடுத்துக்காட்டாக light அல்லது dark mode, accent colors, அல்லது font scaling).
- Authentication state (தற்போதைய user object, login status, அல்லது session expiry போன்ற).
- Language and locale (சர்வதேசமயமாக்கலுக்கான (internationalization) அமைப்புகள்).
- Shopping cart data (header badge, ஒரு mini-cart dropdown மற்றும் checkout page ஆகியவற்றில் ஒரே நேரத்தில் மாற வேண்டிய தரவு).
ஒவ்வொரு local state-ஐயும் Context-க்குள் கொட்ட வேண்டும் என்ற தூண்டுதலைத் தவிர்க்கவும். இரண்டு நிலைகள் கீழே உள்ள ஒரு form input-க்கு உலகளாவிய அறிவிப்பு (global broadcast) தேவையில்லை. உண்மையான cross-cutting concerns-களுக்கு Context-ஐப் பயன்படுத்துங்கள், மற்றவற்றை சாதாரண props-களாகவே விடவும்.
செயல்திறன் சிக்கல்கள் மற்றும் அவற்றைத் தவிர்ப்பது எப்படி
Context இலவசமானது அல்ல. ஒரு context மதிப்பு புதுப்பிக்கப்படும்போது, அந்த context-உடன் இணைக்கப்பட்டுள்ள ஒவ்வொரு component-உம் மீண்டும் render ஆகும் (re-renders), அவர்களுக்குத் தேவையான மதிப்பு மாறாவிட்டாலும் கூட. ஒவ்வொரு parent render-இன் போதும் Provider-க்குள் ஒரு புதிய object literal-ஐத் 던க்கும் (tossing) பழக்கமே பொதுவான தவறு.
நமது தீம் உதாரணத்தில், ThemeProvider-இன் parent புதுப்பிக்கப்படுவதால் ஒவ்வொரு முறை ThemeProvider re-render ஆகும்போதும், { theme, setTheme } என்ற expression ஒரு புதிய object-ஐ உருவாக்குகிறது. React ஒரு புதிய reference-ஐக் காண்பதால், ஒவ்வொரு consumer-உம் புதுப்பிக்கப்படுகிறது. உங்கள் தீம் அரிதாகவே மாறுகிறது ஆனால் உங்கள் app state அடிக்கடி மாறுகிறது என்றால், உங்களுக்குத் தேவையில்லாத render-களுக்காக நீங்கள் விலை கொடுக்கிறீர்கள்.
இதற்கு இரண்டு தீர்வுகள் உள்ளன.
உங்கள் contexts-களை புதுப்பிக்கும் அதிர்வெண் (update frequency) அடிப்படையில் பிரிக்கவும். ஒரு முறை login செய்யும் போது மட்டும் மாறும் UserContext, ஒவ்வொரு சில வினாடிகளுக்கும் புதுப்பிக்கப்படும் NotificationContext-உடன் ஒரே provider-ஐப் பகிர்ந்து கொள்ளக்கூடாது. நிலையான தரவு (static data), அடிக்கடி மாறும் தரவுடன் (volatile data) சேர்ந்து ஒரே re-render முறையில் சிக்காமல் இருக்க அவற்றைத் தனித்தனியாக வைத்திருக்கவும்.
மதிப்பு ஒரு object அல்லது array ஆக இருக்கும்போது, அதை useMemo-க்குள் சுற்றவும். React-க்கு ஒரு நிலையான reference-ஐ வழங்கவும்:
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
இப்போது theme உண்மையில் மாறும்போது மட்டுமே object identity மாறும். context-ஐப் பயன்படுத்தும், ஆனால் கீழே React.memo மூலம் பாதுகாக்கப்பட்டிருக்கும் துணை கூறுகள் (descendant components) அந்த வேலையைத் தவிர்க்கும்.
உங்கள் நேரத்தை வீணடிக்கும் தவறுகள்
production code-இல் இன்னும் வெளிப்படும் அந்த இரண்டு பிழைகளையும் எளிதாகத் தவிர்க்கலாம்.
முதலாவதாக, context-ஐயே export செய்ய மறந்துவிடுவது. நீங்கள் Provider wrapper-ஐ மட்டும் export செய்துவிட்டு, context object-ஐத் தனியாக (private) வைத்திருந்தால், ஒரு புதிய feature-ஐ உருவாக்கும் developer உங்கள் module-ஐ மாற்றியமைக்காமல் (refactoring) useContext-ஐ அழைக்க முடியாது. consumers எளிதாக provider மற்றும் consumer hook ஆகிய இரண்டையும் இறக்குமதி (import) செய்ய ஏதுவாக context-ஐ export செய்யுங்கள்.
இரண்டாவதாக, அதற்குரிய Provider-க்கு வெளியே useContext-ஐ அழைப்பது. ThemeToggle என்பது ThemeProvider-க்குள் இல்லாத ஒரு கிளையில் (branch) render செய்யப்பட்டால், அந்த hook createContext-க்கு வழங்கப்பட்ட default மதிப்பையோ அல்லது நீங்கள் எதையும் வழங்கவில்லை என்றால் undefined-ஐயோ வழங்கும். இது cannot read property of undefined போன்ற அமைதியான தோல்விகளுக்கு (silent failures) வழிவகுக்கும். ஒரு பொருத்தமான default மதிப்பைக் கொடுப்பதன் மூலமோ அல்லது hook அழைப்பின் தொடக்கத்திலேயே ஒரு தெளிவான பிழையை (error) வெளியிடுவதன் மூலமோ இதைத் தவிர்க்கலாம்.
Context vs Redux: எளிமையாக வைத்திருங்கள்
உங்களுக்கு எப்போதும் Redux தேவையில்லை. நடுத்தர அளவிலான திட்டங்களுக்கு (medium-sized projects), Context மற்றும் useState அல்லது useReducer ஆகியவற்றின் இணைப்பே பெரும்பாலான state sharing தேவைகளைப் பூர்த்தி செய்துவிடும். time-travel debugging, சிக்கலான middleware, அல்லது வரிசையாகத் திரும்பப் பெறப்பட வேண்டிய (roll back) global transactions போன்ற தேவைகள் இருக்கும்போது Redux சிறப்பாகச் செயல்படும். உங்கள் முழு state-உம் ஒரு user object, ஒரு theme string மற்றும் ஒரு cart array ஆகியவற்றை மட்டுமே கொண்டிருந்தால், ஒரு store library தேவையில்லாத boilerplate குறியீடுகளை மட்டுமே சேர்க்கும்.
இருப்பினும், Context என்பது ஒரு முழுமையான state management system அல்ல. இது உங்களுக்கு ஒற்றை global snapshot-ஐ வழங்காது, மேலும் தொடர்பில்லாத context-களுக்கு இடையே updates-களை batch முறையில் செய்யாது. இதை prop drilling-க்கு மாற்றாகப் பயன்படுத்துங்கள், உங்கள் முழு data layer-க்கும் ஒரு operating system ஆகப் பயன்படுத்தாதீர்கள்.
முக்கியக் கருத்து
தேவையில்லாத components வழியாக props-களைக் கடத்துவதை நிறுத்துங்கள். உங்கள் tree-யில் பரவியிருக்கும் தரவுகளுக்குத் தனித்துவமான (focused) contexts-களை உருவாக்குங்கள், consumers-களை உள்ளடக்கும் வகையில் providers-களை உயரத்தில் (high enough) வையுங்கள், மேலும் collections அல்லது functions-களைப் பகிரும்போது value object-ஐ எப்போதும் நிலையானதாக (stabilize) வைத்திருங்கள். Context API உங்கள் React code-ஐ நேரடியாக வைத்திருக்கும்: props உள்ளூர் நிலையிலேயே (local) இருக்கும், global தரவுகள் தடையின்றிப் பயணிக்கும், மேலும் உங்கள் component boundaries சுத்தமாக இருக்கும்.
