javascript - गलत श्रोता घटना श्रोता के साथ व्यवहार को झुकाता है



reactjs react-hooks (1)

कार्यात्मक घटकों के लिए यह सामान्य समस्या है जो useState हुक का उपयोग useState हैं। वही चिंताएँ किसी भी कॉलबैक फ़ंक्शंस पर लागू होती हैं, जहाँ useState का उपयोग किया जाता है, जैसे useState या setInterval टाइमर फ़ंक्शंस

इवेंट हैंडलर का इलाज CardsProvider और Card घटकों में अलग-अलग तरीके से किया जाता है।

handleCardClick कार्यात्मक घटक में उपयोग किया गया handleCardClick और handleCardClick इसके दायरे में परिभाषित किया गया है। जब भी यह चलता है तो हर बार नए कार्य होते हैं, वे cards स्टेट्स का उल्लेख करते cards जो उस समय प्राप्त किया गया था जब उन्हें परिभाषित किया गया था। इवेंट हैंडलर को हर बार तब पंजीकृत किया जाता है, जब CardsProvider घटक प्रदान किया जाता है।

Card फंक्शनल कंपोनेंट में उपयोग किए जाने वाले handleCardClick को एक handleCardClick रूप में प्राप्त किया जाता है और एक बार रजिस्टर किया useEffect । यह पूरे घटक जीवनकाल के दौरान एक ही कार्य है और बासी अवस्था को संदर्भित करता है जो उस समय ताजा था जब पहली बार handleCardClick फ़ंक्शन को परिभाषित किया गया था। handleButtonClick प्रत्येक Card रेंडर पर एक प्रोप और फिर से पंजीकृत के रूप में प्राप्त होता है, यह हर बार एक नया फ़ंक्शन होता है और ताज़ा स्थिति को संदर्भित करता है।

परस्पर अवस्था

एक आम तरीका जो इस समस्या को हल करता है वह है useRef का उपयोग करने के बजाय उपयोग useState । रेफरी मूल रूप से एक रेसिपी है जो एक ऐसी परिवर्तनशील वस्तु प्रदान करती है जिसे संदर्भ द्वारा पारित किया जा सकता है:

const ref = useRef(0);

function eventListener() {
  ref.current++;
}

मामले में एक घटक को राज्य अद्यतन पर फिर से प्रस्तुत किया जाना चाहिए जैसे कि यह उपयोग से useState , फिर भी लागू नहीं होता है।

राज्य के अद्यतन और परिवर्तनशील स्थिति को अलग-अलग रखना संभव है, लेकिन forceUpdate को क्लास और फ़ंक्शन घटकों (केवल संदर्भ के लिए सूचीबद्ध) दोनों में एक forceUpdate माना जाता है:

const useForceUpdate = () => {
  const [, setState] = useState();
  return () => setState({});
}

const ref = useRef(0);
const forceUpdate = useForceUpdate();

function eventListener() {
  ref.current++;
  forceUpdate();
}

स्टेट अपडेटर फंक्शन

एक समाधान राज्य अद्यतनकर्ता फ़ंक्शन का उपयोग करना है जो बासी स्थिति के बजाय नए राज्य को घेरने की गुंजाइश से प्राप्त करता है:

function eventListener() {
  // doesn't matter how often the listener is registered
  setState(freshState => freshState + 1);
}

यदि राज्य को console.log समान सिंक्रोनस साइड इफेक्ट की आवश्यकता होती है, तो अपडेट को रोकने के लिए उसी स्थिति को वापस करने के लिए वर्कअराउंड किया जाता है।

function eventListener() {
  setState(freshState => {
    console.log(freshState);
    return freshState;
  });
}

useEffect(() => {
  // register eventListener once
}, []);

यह अतुल्यकालिक साइड इफेक्ट के साथ अच्छी तरह से काम नहीं करता है, विशेष रूप से async फ़ंक्शन।

मैनुअल ईवेंट श्रोता पुन: पंजीकरण

एक और उपाय यह है कि हर बार इवेंट श्रोता को फिर से पंजीकृत किया जाए, इसलिए एक कॉलबैक हमेशा एनक्लोजिंग स्कोप से ताज़ा स्थिति प्राप्त करता है:

function eventListener() {
  console.log(state);
}

useEffect(() => {
  // register eventListener on each state update
}, [state]);

अंतर्निहित ईवेंट हैंडलिंग

जब तक ईवेंट श्रोता document , window या अन्य ईवेंट लक्ष्य पर पंजीकृत नहीं होता document , वर्तमान घटक के दायरे से बाहर होता है, तो रिएक्ट के स्वयं के डोम ईवेंट हैंडलिंग को जहां संभव हो, वहां उपयोग करना पड़ता है, इससे उपयोग की आवश्यकता समाप्त हो useEffect :

<button onClick={eventListener} />

अंतिम स्थिति में ईवेंट श्रोता को इसके उपयोग के रूप में useMemo किया जा सकता है, जब यह एक प्रोप के रूप में पारित हो जाता है तो अनावश्यक री- useCallback को रोकने के लिए उपयोग useMemo या useCallback कॉलबैक के साथ useMemo किया जा सकता है:

const eventListener = useCallback(() => {
  console.log(state);
}, [state]);

प्रतिक्रियाशील 16.7.0-अल्फा संस्करण में प्रारंभिक useState हुक कार्यान्वयन के लिए लागू है, लेकिन अंतिम प्रतिक्रिया 16.8 कार्यान्वयन में व्यावहारिक नहीं है, जो परस्पर उपयोग करने के लिए सुझाए गए उत्तर का पिछला संस्करण। useState वर्तमान में केवल अपरिवर्तनीय स्थिति का समर्थन करता है।

मैं रिएक्ट हुक के साथ खेल रहा हूं और एक समस्या का सामना कर रहा हूं। यह गलत स्थिति दिखाता है जब मैं इवेंट श्रोता द्वारा नियंत्रित बटन का उपयोग करके इसे लॉग इन करने की कोशिश कर रहा हूं।

कोडसैंडबॉक्स: https://codesandbox.io/s/lrxw1wr97m

  1. 2 बार 'कार्ड जोड़ें' बटन पर क्लिक करें
  2. पहले कार्ड में, Button1 पर क्लिक करें और कंसोल में देखें कि राज्य में 2 कार्ड हैं (सही व्यवहार)
  3. पहले कार्ड में, Button2 पर क्लिक करें (ईवेंट श्रोता द्वारा नियंत्रित) और कंसोल में देखें कि राज्य में केवल 1 कार्ड है (गलत व्यवहार)

यह गलत स्थिति क्यों दिखाता है? पहले कार्ड में, Button2 को कंसोल में 2 कार्ड प्रदर्शित करना चाहिए। कोई विचार?

import React, { useState, useContext, useRef, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";

const CardsContext = React.createContext();

const CardsProvider = props => {
  const [cards, setCards] = useState([]);

  const addCard = () => {
    const id = cards.length;
    setCards([...cards, { id: id, json: {} }]);
  };

  const handleCardClick = id => console.log(cards);
  const handleButtonClick = id => console.log(cards);

  return (
    <CardsContext.Provider
      value={{ cards, addCard, handleCardClick, handleButtonClick }}
    >
      {props.children}
    </CardsContext.Provider>
  );
};

function App() {
  const { cards, addCard, handleCardClick, handleButtonClick } = useContext(
    CardsContext
  );

  return (
    <div className="App">
      <button onClick={addCard}>Add card</button>
      {cards.map((card, index) => (
        <Card
          key={card.id}
          id={card.id}
          handleCardClick={() => handleCardClick(card.id)}
          handleButtonClick={() => handleButtonClick(card.id)}
        />
      ))}
    </div>
  );
}

function Card(props) {
  const ref = useRef();

  useEffect(() => {
    ref.current.addEventListener("click", props.handleCardClick);
    return () => {
      ref.current.removeEventListener("click", props.handleCardClick);
    };
  }, []);

  return (
    <div className="card">
      Card {props.id}
      <div>
        <button onClick={props.handleButtonClick}>Button1</button>
        <button ref={node => (ref.current = node)}>Button2</button>
      </div>
    </div>
  );
}

ReactDOM.render(
  <CardsProvider>
    <App />
  </CardsProvider>,
  document.getElementById("root")
);

मैं रिएक्ट 16.7.0-अल्फा.0 और क्रोम 70.0.3538.110 का उपयोग करता हूं

BTW, अगर मैं रेकैप्स का उपयोग करके कार्डपॉइडर को फिर से लिखता हूं, तो समस्या दूर हो गई है। CodeSandbox वर्ग का उपयोग कर: https://codesandbox.io/s/w2nn3mq9vl





react-hooks