Kodokon kodokon.com

जटिल state के लिए useReducer

एक शुद्ध reducer के साथ जटिल state को व्यवस्थित करें, जो ट्रांज़िशन को केंद्रीकृत करता है और असंभव संयोजनों को खत्म कर देता है।

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

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

जैसे ही कोई कंपोनेंट कई ऐसे state मानों को संभालता है जो साथ-साथ बदलते हैं - एक request की स्थिति, कुछ डेटा, एक error - useState कॉल बढ़ाने से ट्रांज़िशन का तर्क हर event handler में बिखर जाता है। आप लक्षण जानते हैं: सही क्रम में तीन setter कॉल करने होते हैं, और देर-सवेर एक असंगत state बन जाती है, जैसे success की स्थिति के साथ एक non-null error बैठी हो। useReducer ज़िम्मेदारी को उलट देता है: state एक अकेली object बन जाती है, और हर बदलाव एक action से होकर गुज़रता है जिसे एक केंद्रीय फ़ंक्शन, यानी reducer, व्याख्यायित करता है।

JAVASCRIPT
function requestReducer(state, action) {
  switch (action.type) {
    case "start":
      return { status: "loading", data: null, error: null };
    case "resolve":
      return {
        status: "success",
        data: action.data,
        error: null,
      };
    case "reject":
      return {
        status: "error",
        data: null,
        error: action.error,
      };
    default:
      throw new Error("Unknown action: " + action.type);
  }
}
हर ट्रांज़िशन एक पूर्ण, संगत state को फिर से बनाता है।

reducer एक शुद्ध फ़ंक्शन है: यह वर्तमान state और एक action प्राप्त करता है, फिर बिना किसी side effect के अगली state लौटाता है। फ़ायदे तुरंत मिलते हैं: ट्रांज़िशन स्पष्ट और संपूर्ण होते हैं, असंभव states मूल स्तर पर ही खत्म हो जाती हैं (हर case एक संगत object फिर से बनाता है), और तर्क बिखरने के बजाय एक ही जगह पढ़ा जाता है। एक उपयोगी चेतावनी संकेत: यदि आप एक ही setState कॉल के क्रम को दो जगहों पर copy-paste करते हुए पाएं, तो शायद आपके पास एक reducer की कमी है।

JSX
function UserProfile() {
  const [state, dispatch] = useReducer(requestReducer, {
    status: "idle",
    data: null,
    error: null,
  });

  async function load(id) {
    dispatch({ type: "start" });
    try {
      const res = await fetch("/api/users/" + id);
      const data = await res.json();
      dispatch({ type: "resolve", data });
    } catch (error) {
      dispatch({ type: "reject", error });
    }
  }

  return <button onClick={() => load(1)}>Load</button>;
}
कंपोनेंट मंशा व्यक्त करता है, reducer state तय करता है।

useState और useReducer के बीच चुनाव पसंद का मामला नहीं है। सरल, स्वतंत्र मानों के लिए useState रखें; useReducer की ओर तब बढ़ें जब अगली state पिछली पर निर्भर हो या जब कई फ़ील्ड साथ-साथ बदलें। दो अतिरिक्त फ़ायदे: तीसरा argument आलसी initialization देता है (useReducer(reducer, arg, init) केवल पहले render पर init चलाता है), और React गारंटी देता है कि dispatch एक render से दूसरे render तक वही identity बनाए रखे - आप इसे बिना useCallback के children को पास कर सकते हैं।

ज्ञान जांच

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

  1. कौन सा संकेत आपको कई useState कॉल के बजाय useReducer की ओर इशारा करना चाहिए?
    • कंपोनेंट सौ लाइनों से अधिक हो जाता है
    • कई state मान साथ-साथ बदलते हैं और नई state पिछली पर निर्भर करती है
    • कंपोनेंट तीन से अधिक props प्राप्त करता है
    • state में एक array होता है
  2. एक reducer उसे मिली state object को, जगह पर ही mutate करके, लौटाता है। React क्या करता है?
    • यह पुराने और नए reference की तुलना करता है, उन्हें समान पाता है, और re-render छोड़ देता है
    • यह गहरे mutation का पता लगाता है और कंपोनेंट को फिर से render करता है
    • यह एक error फेंकता है क्योंकि development में state frozen होती है
  3. आप dispatch को एक memoized child को बिना किसी विशेष सावधानी के क्यों पास कर सकते हैं?
    • क्योंकि React.memo function-प्रकार के props को अनदेखा करता है
    • क्योंकि dispatch हर render पर फिर से बनता है लेकिन संरचनात्मक रूप से समान होता है
    • क्योंकि React गारंटी देता है कि dispatch कंपोनेंट के पूरे जीवनकाल के लिए वही identity बनाए रखता है