कंपोनेंट में यूजर्स से स्टेटटर को मल्टीपल कॉल मल्टीपल री-रेंडरर्स का कारण बनता है


122

मैं पहली बार रिएक्ट हुक की कोशिश कर रहा हूं और जब तक मुझे एहसास नहीं हुआ कि मुझे दो अलग-अलग स्टेट वैरिएबल (डेटा और लोडिंग फ्लैग) मिलते हैं, तब तक मेरा कंपोनेंट (डेटा टेबल) दो बार रेंडर हो जाता है, भले ही दोनों कॉल एक ही समारोह में राज्य updater हो रहे हैं। यहाँ मेरा एपी फंक्शन है जो दोनों चर को मेरे घटक पर लौटा रहा है।

const getData = url => {

    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(async () => {

        const test = await api.get('/people')

        if(test.ok){
            setLoading(false);
            setData(test.data.results);
        }

    }, []);

    return { data, loading };
};

एक सामान्य श्रेणी के घटक में आप राज्य को अपडेट करने के लिए एक एकल कॉल करेंगे जो एक जटिल वस्तु हो सकती है लेकिन "हुक तरीका" राज्य को छोटी इकाइयों में विभाजित करने के लिए प्रतीत होता है, जिसका एक साइड इफेक्ट कई बार लगता है- रेंडरर्स जब वे अलग से अपडेट किए जाते हैं। किसी भी विचार यह कैसे कम करने के लिए?


यदि आपने राज्यों पर निर्भर किया है तो आपको शायद इस्तेमाल करना चाहिएuseReducer
थिंकलिनक्स

वाह! मुझे केवल मुझे यह पता चला है और इसने पूरी तरह से मेरी समझ को उड़ा दिया है कि रेंडर कैसे काम करता है। मैं इस तरह से काम करने के लिए किसी भी लाभ को नहीं समझ सकता - यह बल्कि मनमाना लगता है कि एक async कॉलबैक में व्यवहार सामान्य सामान्य हैंडलर से अलग है। BTW, मेरे परीक्षणों में ऐसा लगता है कि सुलह (यानी असली DOM का अपडेट) तब तक नहीं होता है जब तक सभी setState कॉल को संसाधित नहीं किया जाता है, इसलिए मध्यवर्ती रेंडर कॉल वैसे भी बर्बाद हो जाते हैं।
एंडी

जवाबों:


121

आप loadingराज्य और dataराज्य को एक राज्य ऑब्जेक्ट में मिला सकते हैं और फिर आप एक setStateकॉल कर सकते हैं और केवल एक ही रेंडर होगा।

नोट: विपरीत setStateवर्ग घटकों में, setStateसे लौटे useStateमौजूदा राज्य के साथ वस्तुओं विलय नहीं है, यह पूरी तरह से वस्तु बदल देता है। यदि आप मर्ज करना चाहते हैं, तो आपको पिछली स्थिति को पढ़ना होगा और इसे नए मूल्यों के साथ स्वयं मर्ज करना होगा। डॉक्स का संदर्भ लें ।

जब तक आपने निर्धारित नहीं किया है कि आपको प्रदर्शन समस्या है, तब तक मैं अत्यधिक कॉल करने वालों के बारे में बहुत अधिक चिंता नहीं करूंगा। (प्रतिक्रिया के संदर्भ में) प्रतिपादन और वास्तविक डोम के लिए आभासी डोम अद्यतन अलग-अलग मामले हैं। यहाँ रेंडरिंग वर्चुअल DOM जनरेट करने की बात कर रहा है, और ब्राउज़र DOM को अपडेट करने के बारे में नहीं। प्रतिक्रिया setStateकॉल को बैच सकती है और अंतिम नए राज्य के साथ ब्राउज़र डोम को अपडेट कर सकती है।

const {useState, useEffect} = React;

function App() {
  const [userRequest, setUserRequest] = useState({
    loading: false,
    user: null,
  });

  useEffect(() => {
    // Note that this replaces the entire object and deletes user key!
    setUserRequest({ loading: true });
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        setUserRequest({
          loading: false,
          user: data.results[0],
        });
      });
  }, []);

  const { loading, user } = userRequest;

  return (
    <div>
      {loading && 'Loading...'}
      {user && user.name.first}
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>

वैकल्पिक - अपना राज्य विलय हुक लिखें

const {useState, useEffect} = React;

function useMergeState(initialState) {
  const [state, setState] = useState(initialState);
  const setMergedState = newState => 
    setState(prevState => Object.assign({}, prevState, newState)
  );
  return [state, setMergedState];
}

function App() {
  const [userRequest, setUserRequest] = useMergeState({
    loading: false,
    user: null,
  });

  useEffect(() => {
    setUserRequest({ loading: true });
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        setUserRequest({
          loading: false,
          user: data.results[0],
        });
      });
  }, []);

  const { loading, user } = userRequest;

  return (
    <div>
      {loading && 'Loading...'}
      {user && user.name.first}
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>


1
मैं मानता हूं कि यह आदर्श नहीं है, लेकिन यह अभी भी इसे करने का एक उचित तरीका है। आप अपने स्वयं के कस्टम हुक को लिख सकते हैं जो विलय करता है यदि आप स्वयं विलय नहीं करना चाहते हैं। मैंने स्पष्ट होने के लिए अंतिम वाक्य को अपडेट किया। क्या आप इससे परिचित हैं कि रिएक्ट कैसे काम करता है? यदि नहीं, तो यहां कुछ लिंक दिए गए हैं जो आपको बेहतर समझने में मदद कर सकते हैं - reactjs.org/docs/reconciliation.html , blog.vjeux.com/2013/javascript/react-performance.html
यांगशुन टीआई

2
@jonhobbs मैंने एक वैकल्पिक उत्तर जोड़ा है जो एक useMergeStateहुक बनाता है ताकि आप स्वचालित रूप से राज्य का विलय कर सकें।
यंगशुन तैय डे

1
शानदार, धन्यवाद फिर से। मैं रिएक्ट का उपयोग करने के साथ अधिक परिचित हूं, लेकिन इसके बारे में बहुत कुछ सीख सकता हूं। मैं उन्हें पढ़ने के लिए दे दूँगा।
जॉन्हब्स

2
किन परिस्थितियों में यह सच हो सकता है ?: " रिएक्ट setStateकॉल को बैच सकता है और अंतिम नई स्थिति के साथ ब्राउज़र DOM को अपडेट कर सकता है। "
Ecoe

1
अच्छा .. मैंने राज्य के संयोजन के बारे में सोचा, या एक अलग पेलोड राज्य का उपयोग किया, और अन्य राज्यों को पेलोड ऑब्जेक्ट से पढ़ा। हालांकि बहुत अच्छी पोस्ट। 10/10 फिर से पढ़ेगा
एंथनी मून बीम टुट्री

61

यह भी एक और समाधान का उपयोग कर रहा है useReducer! पहले हम अपने नए को परिभाषित करते हैं setState

const [state, setState] = useReducer(
  (state, newState) => ({...state, ...newState}),
  {loading: true, data: null, something: ''}
)

उसके बाद हम केवल अच्छे पुराने वर्गों की तरह इसका उपयोग कर सकते हैं this.setState, केवल बिना this!

setState({loading: false, data: test.data.results})

जैसा कि आप हमारे नए में देख सकते हैं setState(जैसे कि हमारे पास पहले जैसा था this.setState), हमें सभी राज्यों को एक साथ अपडेट करने की आवश्यकता नहीं है! उदाहरण के लिए मैं इस तरह से हमारे राज्यों में से एक को बदल सकता हूं (और यह अन्य राज्यों को बदल नहीं सकता है!):

setState({loading: false})

बहुत बढ़िया, हा ?!

तो आइए सभी टुकड़ों को एक साथ रखें:

import {useReducer} from 'react'

const getData = url => {
  const [state, setState] = useReducer(
    (state, newState) => ({...state, ...newState}),
    {loading: true, data: null}
  )

  useEffect(async () => {
    const test = await api.get('/people')
    if(test.ok){
      setState({loading: false, data: test.data.results})
    }
  }, [])

  return state
}

Expected 0 arguments, but got 1.ts(2554)एक त्रुटि है जो मुझे इस तरह से उपयोग करने की कोशिश कर रहा है। अधिक ठीक है setState। आप इसे कैसे ठीक करते हैं? फ़ाइल js है, लेकिन हम अभी भी ts चेक का उपयोग करते हैं।
ZenVentzi

बहुत बढ़िया भाई, धन्यवाद
निश्रग

मुझे लगता है कि यह अभी भी एक ही विचार है कि दो राज्यों का विलय हो गया। लेकिन कुछ मामलों में, आप राज्यों का विलय नहीं कर सकते।
user1888955

45

रिएक्शन-हुक में बैचिंग अपडेट https://github.com/facebook/react/issues/14259

यदि वे प्रतिक्रिया-आधारित घटना के भीतर से एक बटन क्लिक या इनपुट परिवर्तन की तरह ट्रिगर हो जाते हैं, तो प्रतिक्रिया वर्तमान में बैच अपडेट अपडेट करेगी। अगर वे एक रिएक्ट इवेंट हैंडलर के बाहर ट्रिगर कर रहे हैं, तो यह async कॉल की तरह, बैच अपडेट नहीं करेगा।


5
यह इस तरह का एक उपयोगी उत्तर है, क्योंकि यह कुछ भी करने के बिना संभवतः अधिकांश मामलों में समस्या को हल करता है। धन्यवाद!
डेवनिकविल


4

यदि आप तृतीय-पक्ष हुक का उपयोग कर रहे हैं और राज्य को एक वस्तु या उपयोग में नहीं मिला सकते हैं useReducer, तो समाधान का उपयोग करना है:

ReactDOM.unstable_batchedUpdates(() => { ... })

डैन अब्रामोव द्वारा अनुशंसित यहाँ

इस उदाहरण को देखें


लगभग एक साल बाद यह सुविधा अभी भी पूरी तरह से अनजानी है और अभी भी अस्थिर के रूप में चिह्नित है :-(
एंडी

4

दोहराने के लिए this.setStateवर्ग घटकों से मर्ज व्यवहार, प्रतिक्रिया डॉक्स की सिफारिश के कार्यात्मक फार्म का उपयोग करने के लिए useStateवस्तु प्रसार के साथ - कोई जरूरत के लिए useReducer:

setState(prevState => {
  return {...prevState, loading, data};
});

दो राज्यों को अब एक में समेकित किया गया है, जो आपको एक रेंडर चक्र को बचाएगा।

वहाँ एक राज्य वस्तु के साथ एक और लाभ यह है: loadingऔर dataकर रहे हैं निर्भर राज्यों। जब राज्य को एक साथ रखा जाता है, तो अमान्य राज्य परिवर्तन अधिक स्पष्ट हो जाते हैं:

setState({ loading: true, data }); // ups... loading, but we already set data

आप भी बेहतर कर सकते हैं सुनिश्चित लगातार राज्यों 1. द्वारा) बनाने की स्थिति - loading, success, error, आदि - स्पष्ट अपने राज्य और 2) का उपयोग करने में useReducerएक कम करने में Encapsulate राज्य तर्क करने के लिए:

const useData = () => {
  const [state, dispatch] = useReducer(reducer, /*...*/);

  useEffect(() => {
    api.get('/people').then(test => {
      if (test.ok) dispatch(["success", test.data.results]);
    });
  }, []);
};

const reducer = (state, [status, payload]) => {
  if (status === "success") return { ...state, data: payload, status };
  // keep state consistent, e.g. reset data, if loading
  else if (status === "loading") return { ...state, data: undefined, status };
  return state;
};

const App = () => {
  const { data, status } = useData();
  return status === "loading" ? <div> Loading... </div> : (
    // success, display data 
  )
}

पुनश्च: (के बजाय ) के साथ कस्टम हुक को उपसर्ग करना सुनिश्चित करें । साथ ही कॉलबैक पास नहीं किया जा सकता है ।useuseDatagetDatauseEffectasync


1
इस अप! यह रिएक्ट एपीआई में मुझे मिली सबसे उपयोगी विशेषताओं में से एक है। यह तब मिला जब मैंने एक फ़ंक्शन को एक राज्य के रूप में संग्रहीत करने की कोशिश की (मुझे पता है कि यह फ़ंक्शन को संग्रहीत करने के लिए अजीब है, लेकिन मुझे किसी कारण से मुझे याद नहीं है) और यह आरक्षित कॉल हस्ताक्षर के कारण अपेक्षित रूप से काम नहीं किया
elquimista

1

Https://stackoverflow.com/a/53575023/121143 पर जवाब देने के लिए थोड़ा अतिरिक्त

ठंडा! जो लोग इस हुक का उपयोग करने की योजना बना रहे हैं, उनके लिए इसे तर्क के रूप में कार्य करने के लिए थोड़ा मजबूत तरीके से लिखा जा सकता है, जैसे कि:

const useMergedState = initial => {
  const [state, setState] = React.useState(initial);
  const setMergedState = newState =>
    typeof newState == "function"
      ? setState(prevState => ({ ...prevState, ...newState(prevState) }))
      : setState(prevState => ({ ...prevState, ...newState }));
  return [state, setMergedState];
};

अद्यतन : अनुकूलित संस्करण, राज्य को संशोधित नहीं किया जाएगा जब आने वाली आंशिक स्थिति को बदला नहीं गया था।

const shallowPartialCompare = (obj, partialObj) =>
  Object.keys(partialObj).every(
    key =>
      obj.hasOwnProperty(key) &&
      obj[key] === partialObj[key]
  );

const useMergedState = initial => {
  const [state, setState] = React.useState(initial);
  const setMergedState = newIncomingState =>
    setState(prevState => {
      const newState =
        typeof newIncomingState == "function"
          ? newIncomingState(prevState)
          : newIncomingState;
      return shallowPartialCompare(prevState, newState)
        ? prevState
        : { ...prevState, ...newState };
    });
  return [state, setMergedState];
};

ठंडा! मैं केवल setMergedStateसाथ ही लपेटूंगा useMemo(() => setMergedState, []), क्योंकि जैसा कि डॉक्स कहते हैं, setStateरी- React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.रेंडरर्स के बीच बदलाव नहीं होता है: इस तरह से सेटस्टैट फ़ंक्शन को री-रेंडरर्स पर दोबारा नहीं बनाया जाता है।
टोनिक्स

0

आप useEffectएक राज्य परिवर्तन का पता लगाने के लिए भी उपयोग कर सकते हैं, और तदनुसार अन्य राज्य मूल्यों को अपडेट कर सकते हैं


क्या आप विस्तार से समझा सकते हैं? वह पहले से ही उपयोग कर रहा है useEffect
bluenote10
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.