compound components, state reducer, और render props बनाम hooks के सही चुनाव के साथ लचीले component APIs डिज़ाइन करें।
इस पाठ को Kodokon में खोलेंएक <Tabs items={...} labels={...} icons={...} /> component हमेशा configuration props के नीचे दबकर रह जाता है। compound components pattern दृष्टिकोण को पलट देता है: कई components सहयोग करते हैं और किसी context के माध्यम से अंतर्निहित state साझा करते हैं, जबकि उपयोगकर्ता के पास JSX संयोजन (क्रम, nesting, बीच में डाले गए elements) का नियंत्रण बना रहता है। यह HTML में <select> और <option> का मॉडल है, और वही जो Radix या Headless UI जैसी libraries द्वारा उपयोग किया जाता है।
const TabsContext = createContext(null);
function Tabs({ defaultTab, children }) {
const [active, setActive] = useState(defaultTab);
const value = useMemo(
() => ({ active, setActive }),
[active]
);
return (
<TabsContext.Provider value={value}>
{children}
</TabsContext.Provider>
);
}हर subcomponent context को पढ़ता है और उसे किसी भी wiring prop की ज़रूरत नहीं होती: उपयोगकर्ता स्वतंत्र रूप से संयोजित करता है, और pattern संगति की गारंटी देता है। ध्यान दें कि context मान useMemo में लपेटा गया है: इसके बिना, माता-पिता का हर render एक नया object बनाता और सभी consumers का render मजबूर कर देता।
function Tab({ id, children }) {
const ctx = useContext(TabsContext);
return (
<button
aria-selected={ctx.active === id}
onClick={() => ctx.setActive(id)}
>
{children}
</button>
);
}
// <Tabs defaultTab="a">
// <Tab id="a">Profile</Tab>
// <Tab id="b">Settings</Tab>
// </Tabs>दूसरा pattern: नियंत्रण का व्युत्क्रमण (inversion of control), जिसका सबसे परिष्कृत रूप Kent C. Dodds द्वारा Downshift में लोकप्रिय किया गया state reducer है। हर नई ज़रूरत के लिए एक boolean prop जोड़ने के बजाय, hook अपने state transitions को उजागर करता है: caller एक reducer प्रदान करता है जो हर action प्राप्त करता है और उसे जाने दे सकता है, बदल सकता है, या रद्द कर सकता है। library अपना default व्यवहार बनाए रखती है; बारीक नियंत्रण consumer का होता है।
function defaultReducer(state, action) {
if (action.type === 'toggle') {
return { on: !state.on };
}
return state;
}
function useToggle({ reducer = defaultReducer } = {}) {
const [state, dispatch] = useReducer(reducer, {
on: false,
});
const toggle = () => dispatch({ type: 'toggle' });
return { on: state.on, toggle };
}अंत में, शाश्वत render props बनाम hooks बहस। तर्क साझा करने के लिए hooks जीत गए हैं: tree में कोई नकली पदानुक्रम नहीं, wrappers का कोई पिरामिड नहीं, कई स्रोतों का स्वाभाविक संयोजन। लेकिन render prop एक वैध क्षेत्र बनाए रखता है: जब भिन्नता rendering से ही संबंधित हो। एक virtualized list जो आपसे पूछती है कि हर row को कैसे बनाना है, एक headless component जो अपना पूरा JSX सौंप देता है: वहाँ, prop के रूप में पास किया गया एक render function सबसे सीधा उपकरण बना रहता है, क्योंकि एक hook यह तय नहीं कर सकता कि caller के tree में JSX कहाँ डालनी है।
function MouseTracker({ render }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
const onMove = (e) =>
setPos({ x: e.clientX, y: e.clientY });
return (
<div onMouseMove={onMove}>{render(pos)}</div>
);
}
// For the logic alone, a hook is enough:
// const pos = useMousePosition();