Kodokon kodokon.com

विशेषज्ञ patterns: compound components, नियंत्रण का व्युत्क्रमण (inversion of control)

compound components, state reducer, और render props बनाम hooks के सही चुनाव के साथ लचीले component APIs डिज़ाइन करें।

10 मिनट · 3 प्रश्न

इस पाठ को Kodokon में खोलें

एक <Tabs items={...} labels={...} icons={...} /> component हमेशा configuration props के नीचे दबकर रह जाता है। compound components pattern दृष्टिकोण को पलट देता है: कई components सहयोग करते हैं और किसी context के माध्यम से अंतर्निहित state साझा करते हैं, जबकि उपयोगकर्ता के पास JSX संयोजन (क्रम, nesting, बीच में डाले गए elements) का नियंत्रण बना रहता है। यह HTML में <select> और <option> का मॉडल है, और वही जो Radix या Headless UI जैसी libraries द्वारा उपयोग किया जाता है।

JSX
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>
  );
}
माता-पिता एक memoized context के माध्यम से साझा state को उजागर करता है।

हर subcomponent context को पढ़ता है और उसे किसी भी wiring prop की ज़रूरत नहीं होती: उपयोगकर्ता स्वतंत्र रूप से संयोजित करता है, और pattern संगति की गारंटी देता है। ध्यान दें कि context मान useMemo में लपेटा गया है: इसके बिना, माता-पिता का हर render एक नया object बनाता और सभी consumers का render मजबूर कर देता।

JSX
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>
कोई wiring props नहीं: context ही जोड़ बनाता है।

दूसरा pattern: नियंत्रण का व्युत्क्रमण (inversion of control), जिसका सबसे परिष्कृत रूप Kent C. Dodds द्वारा Downshift में लोकप्रिय किया गया state reducer है। हर नई ज़रूरत के लिए एक boolean prop जोड़ने के बजाय, hook अपने state transitions को उजागर करता है: caller एक reducer प्रदान करता है जो हर action प्राप्त करता है और उसे जाने दे सकता है, बदल सकता है, या रद्द कर सकता है। library अपना default व्यवहार बनाए रखती है; बारीक नियंत्रण consumer का होता है।

JSX
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 };
}
caller हर state transition को रोक सकता है।

अंत में, शाश्वत render props बनाम hooks बहस। तर्क साझा करने के लिए hooks जीत गए हैं: tree में कोई नकली पदानुक्रम नहीं, wrappers का कोई पिरामिड नहीं, कई स्रोतों का स्वाभाविक संयोजन। लेकिन render prop एक वैध क्षेत्र बनाए रखता है: जब भिन्नता rendering से ही संबंधित हो। एक virtualized list जो आपसे पूछती है कि हर row को कैसे बनाना है, एक headless component जो अपना पूरा JSX सौंप देता है: वहाँ, prop के रूप में पास किया गया एक render function सबसे सीधा उपकरण बना रहता है, क्योंकि एक hook यह तय नहीं कर सकता कि caller के tree में JSX कहाँ डालनी है।

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();
rendering सौंपने के लिए एक render prop, तर्क के लिए एक hook।

ज्ञान जांच

सुनिश्चित करें कि आपको इस पाठ के मुख्य बिंदु याद हैं।

  1. कौन-सा तंत्र compound components को बिना स्पष्ट props के अपनी state साझा करने देता है?
    • एक साझा module variable
    • माता-पिता component द्वारा प्रदान किया गया एक React context
    • हर child में state की नकल करना
    • cloneElement के माध्यम से नीचे पास किए गए refs
  2. state reducer pattern वास्तव में किसका व्युत्क्रमण करता है?
    • माता-पिता और child के बीच data प्रवाह की दिशा
    • state transitions पर नियंत्रण, जो caller को सौंप दिया जाता है
    • hooks का execution क्रम
    • fallback को render करने की ज़िम्मेदारी
  3. किस स्थिति में एक render prop एक hook से बेहतर बना रहता है?
    • जब आप components के बीच तर्क साझा करना चाहते हैं
    • जब caller को component के tree में डालने के लिए JSX प्रदान करनी होती है
    • जब आपको किसी महँगी गणना को memoize करने की ज़रूरत हो
    • जब component में कोई state नहीं होती