पुराने हुक की तुलना कैसे करें और रिएक्ट हुक पर newValues ​​का उपयोग करें?


149

मान लीजिए कि मेरे पास 3 इनपुट हैं: दर, SendAmount, और ReceAmount। मैं उस 3 निविष्टियाँ का उपयोग करता हूं परफ़ेक्ट डिफरेंट पाराम्स। नियम हैं:

  • यदि SendAmount बदल गया है, तो मैं गणना करता हूं receiveAmount = sendAmount * rate
  • यदि प्राप्त राशि बदल गई है, तो मैं गणना करता हूं sendAmount = receiveAmount / rate
  • यदि दर में परिवर्तन हुआ है, तो मैं गणना करता हूं कि मैं receiveAmount = sendAmount * rateकब sendAmount > 0या sendAmount = receiveAmount / rateकब गणना करता हूंreceiveAmount > 0

समस्या को प्रदर्शित करने के लिए यहाँ कोडैंडबॉक्स https://codesandbox.io/s/pkl6vn7x6j है।

क्या इस मामले के लिए 3 हैंडलर बनाने के बजाय तुलना करना oldValuesऔर newValuesपसंद करना एक तरीका है componentDidUpdate?

धन्यवाद


यहाँ मेरा अंतिम समाधान usePrevious https://codesandbox.io/s/30n01w2r06 है

इस स्थिति में, मैं useEffectएक से अधिक का उपयोग नहीं कर सकता क्योंकि प्रत्येक परिवर्तन एक ही नेटवर्क कॉल के लिए अग्रणी है। इसलिए मैं भी changeCountपरिवर्तन को ट्रैक करने के लिए उपयोग करता हूं । यह changeCountकेवल स्थानीय से परिवर्तनों को ट्रैक करने में सहायक है, इसलिए मैं सर्वर से परिवर्तनों के कारण अनावश्यक नेटवर्क कॉल को रोक सकता हूं।


घटक वास्तव में कैसे मदद करने वाला है? आपको अभी भी इन 3 शर्तों को लिखना होगा।
एस्टुस फ्लास्क

मैंने 2 वैकल्पिक समाधानों के साथ एक उत्तर जोड़ा। क्या उनमें से एक आपके लिए काम करता है?
बेन कार्प

जवाबों:


208

आप का उपयोग कर एक पिछले हुक प्रदान करने के लिए एक कस्टम हुक लिख सकते हैं useRef का उपयोग कर

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

और फिर इसमें उपयोग करें useEffect

const Component = (props) => {
    const {receiveAmount, sendAmount } = props
    const prevAmount = usePrevious({receiveAmount, sendAmount});
    useEffect(() => {
        if(prevAmount.receiveAmount !== receiveAmount) {

         // process here
        }
        if(prevAmount.sendAmount !== sendAmount) {

         // process here
        }
    }, [receiveAmount, sendAmount])
}

हालांकि इसका स्पष्ट और शायद बेहतर और स्पष्ट पढ़ने और समझने के लिए यदि आप useEffectप्रत्येक परिवर्तन आईडी के लिए दो अलग-अलग उपयोग करते हैं जो आप उन्हें अलग से संसाधित करना चाहते हैं


8
useEffectअलग से दो कॉल का उपयोग करने पर नोट के लिए धन्यवाद । पता नहीं था कि आप इसे कई बार इस्तेमाल कर सकते हैं!
जेसन

3
मैंने ऊपर दिए गए कोड की कोशिश की, लेकिन एस्लिन मुझे उस useEffectलापता निर्भरता के बारे में चेतावनी दे रहा हैprevAmount
लिटिल

2
चूंकि preAmount राज्य / प्रॉप्स के पिछले मूल्य के लिए मूल्य रखता है, इसलिए आपको इसका उपयोग करने के लिए एक भरोसेमंद के रूप में पारित करने की आवश्यकता नहीं है और आप इस चेतावनी को विशेष मामले के लिए अक्षम कर सकते हैं। आप अधिक जानकारी के लिए इस पोस्ट को पढ़ सकते हैं?
शुभम खत्री

इस प्यार - useRef हमेशा बचाव के लिए आता है।
फर्नांडो रोजो

@ शुभम खत्री: Ref.current = मूल्य के उपयोग की पुष्टि के कारण क्या है?
कर्ली_ब्रैक

40

किसी को भी उपयोग के टाइपस्क्रिप्ट संस्करण की तलाश में रखें।

import { useEffect, useRef } from "react";

const usePrevious = <T extends {}>(value: T): T | undefined => {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

2
ध्यान दें कि यह TSX फ़ाइल में काम नहीं करेगा, ताकि const usePrevious = <T extends any>(...दुभाषिया बनाने के लिए TSX फ़ाइल परिवर्तन में काम करने के लिए यह सोच सकें कि <T>
JSX

1
क्या आप बता सकते हैं कि <T extends {}>काम क्यों नहीं होगा। यह मेरे लिए काम कर रहा है, लेकिन मैं इसे इस तरह उपयोग करने की जटिलता को समझने की कोशिश कर रहा हूं।
कार्तिकेयन_काँ

.tsxफ़ाइलों में jsx होता है जो HTML के समान होता है। यह संभव है कि जेनेरिक <T>एक असम्बद्ध घटक टैग हो।
13

वापसी के प्रकार के बारे में क्या? मुझे मिलता हैMissing return type on function.eslint(@typescript-eslint/explicit-function-return-type)
सबा आहंग

@SabaAhang क्षमा करें! टीएस अधिकांश भाग के लिए रिटर्न प्रकारों को संभालने के लिए टाइप इंट्रैक्शन का उपयोग करेगा, लेकिन यदि आपने लिनिंग सक्षम किया है तो मैंने चेतावनी से छुटकारा पाने के लिए रिटर्न टाइप जोड़ा है।
सेरेडॉम

25

विकल्प 1 - उपयोगकॉम्प हुक

एक मौजूदा मूल्य की तुलना पिछले मूल्य से करना एक सामान्य पैटर्न है, और यह स्वयं के कस्टम हुक को सही ठहराता है जो कार्यान्वयन विवरण को छुपाता है।

const useCompare = (val: any) => {
    const prevVal = usePrevious(val)
    return prevVal !== val
}

const usePrevious = (value) => {
    const ref = useRef();
    useEffect(() => {
      ref.current = value;
    });
    return ref.current;
}

const Component = (props) => {
  ...
  const hasVal1Changed = useCompare(val1)
  const hasVal2Changed = useCompare(val2);
  useEffect(() => {
    if (hasVal1Changed ) {
      console.log("val1 has changed");
    }
    if (hasVal2Changed ) {
      console.log("val2 has changed");
    }
  });

  return <div>...</div>;
};

डेमो

विकल्प 2 - मान बदलने पर उपयोग को चलाएं

const Component = (props) => {
  ...
  useEffect(() => {
    console.log("val1 has changed");
  }, [val1]);
  useEffect(() => {
    console.log("val2 has changed");
  }, [val2]);

  return <div>...</div>;
};

डेमो


दूसरा विकल्प मेरे लिए काम कर गया। क्या आप मुझे मार्गदर्शन दे सकते हैं कि मुझे दो बार लिखने की आवश्यकता क्यों है?
तरुण नागपाल

@TarunNagpal, आपको दो बार उपयोग करने की आवश्यकता नहीं है। यह आपके उपयोग के मामले पर निर्भर करता है। कल्पना करें कि हम बस लॉग करना चाहते हैं कि कौन सी घाटी बदल गई है। यदि हमारे पास निर्भरता के हमारे सरणी में val1 और val2 दोनों हैं, तो यह हर बार चलेगा जब कोई भी मान बदल गया है। फिर हमारे द्वारा पास किए जाने वाले फ़ंक्शन के अंदर हमें यह पता लगाना होगा कि सही संदेश को लॉग करने के लिए कौन सी वैल बदल गई है।
बेन कार्प ने

उत्तर के लिए धन्यवाद। जब आप कहते हैं "फ़ंक्शन के अंदर हम पास करते हैं तो हमें यह पता लगाना होगा कि किस वैल में बदलाव हुआ है"। हम इसे कैसे हासिल कर सकते हैं। कृपया मार्गदर्शन करें
तरुण नागपाल

6

मैंने सिर्फ प्रतिक्रिया-डेल्टा प्रकाशित किया है जो इस सटीक प्रकार के परिदृश्य को हल करता है। मेरी राय में, useEffectबहुत सारी जिम्मेदारियां हैं।

जिम्मेदारियों

  1. यह अपने निर्भरता सरणी में सभी मूल्यों की तुलना करता है Object.is
  2. यह # 1 के परिणाम के आधार पर प्रभाव / सफाई कॉलबैक चलाता है

जिम्मेदारियों को तोड़ना

react-deltauseEffectकई छोटे हुक में जिम्मेदारियों को तोड़ता है ।

जिम्मेदारी # 1

जिम्मेदारी # 2

मेरे अनुभव में, यह दृष्टिकोण useEffect/ useRefसमाधानों की तुलना में अधिक लचीला, स्वच्छ और संक्षिप्त है ।


बस मैं जो ढूंढ रहा हूं। यह कोशिश करो!
Hackerl33t

वास्तव में अच्छा लग रहा है, लेकिन यह थोड़ा अधिक नहीं है?
हिसातो

@ यह बहुत अच्छी तरह से overkill हो सकता है। यह प्रायोगिक एपीआई के बारे में कुछ है। और स्पष्ट रूप से मैंने इसे अपनी टीमों के भीतर ज्यादा इस्तेमाल नहीं किया है क्योंकि यह व्यापक रूप से ज्ञात या अपनाया नहीं गया है। सिद्धांत रूप में यह अच्छा लगता है, लेकिन व्यवहार में यह इसके लायक नहीं हो सकता है।
ऑस्टिन मालेर्बा

3

चूंकि राज्य कार्यात्मक घटकों में घटक उदाहरण के साथ कसकर युग्मित नहीं है, इसलिए पिछली स्थिति को useEffectपहले इसे सहेजे बिना नहीं पहुँचा जा सकता है , उदाहरण के लिए, के साथ useRef। इसका मतलब यह भी है कि राज्य अद्यतन को गलत तरीके से गलत तरीके से लागू किया गया था क्योंकि पिछले राज्य setStateअपडेटर फ़ंक्शन के अंदर उपलब्ध है ।

यह एक अच्छा उपयोग मामला है useReducerजिसके लिए Redux जैसी दुकान प्रदान करता है और संबंधित पैटर्न को लागू करने की अनुमति देता है। राज्य अपडेट स्पष्ट रूप से किए जाते हैं, इसलिए यह पता लगाने की कोई आवश्यकता नहीं है कि कौन सी राज्य संपत्ति अपडेट की गई है; यह पहले से ही प्रेषित कार्रवाई से स्पष्ट है।

यहाँ एक उदाहरण है कि यह कैसा दिख सकता है:

function reducer({ sendAmount, receiveAmount, rate }, action) {
  switch (action.type) {
    case "sendAmount":
      sendAmount = action.payload;
      return {
        sendAmount,
        receiveAmount: sendAmount * rate,
        rate
      };
    case "receiveAmount":
      receiveAmount = action.payload;
      return {
        sendAmount: receiveAmount / rate,
        receiveAmount,
        rate
      };
    case "rate":
      rate = action.payload;
      return {
        sendAmount: receiveAmount ? receiveAmount / rate : sendAmount,
        receiveAmount: sendAmount ? sendAmount * rate : receiveAmount,
        rate
      };
    default:
      throw new Error();
  }
}

function handleChange(e) {
  const { name, value } = e.target;
  dispatch({
    type: name,
    payload: value
  });
}

...
const [state, dispatch] = useReducer(reducer, {
  rate: 2,
  sendAmount: 0,
  receiveAmount: 0
});
...

हाय @estus, इस विचार के लिए धन्यवाद। यह मुझे सोचने का एक और तरीका देता है। लेकिन, मैं यह उल्लेख करना भूल गया कि मुझे प्रत्येक मामले के लिए एपीआई को कॉल करने की आवश्यकता है। क्या आपके पास इसका कोई हल है?
रिंझंग

आपका क्या अर्थ है? अंदर संभाल? क्या 'एपीआई' का मतलब दूरस्थ एपीआई समापन बिंदु है? क्या आप विवरण के साथ उत्तर से कोडैंडबॉक्स अपडेट कर सकते हैं?
एस्टुस फ्लास्क

अपडेट से मैं मान सकता हूं कि आप sendAmountउन्हें गणना करने के बजाय अतुल्यकालिक रूप से प्राप्त करना चाहते हैं, है ना? इससे बहुत कुछ बदल जाता है। ऐसा करना संभव है useReduceलेकिन मुश्किल हो सकता है। यदि आप Redux से निपटते हैं तो इससे पहले कि आप जान सकें कि async ऑपरेशन्स सीधे नहीं हैं। github.com/reduxjs/redux-thunk Redux के लिए एक लोकप्रिय विस्तार है जो async क्रियाओं के लिए अनुमति देता है। यहां एक डेमो दिया गया है जो समान पैटर्न (यूज़टहंकड्यूसर), कोडैंडबॉक्स . io / s / 6z4r79ymwr के साथReducer का उपयोग करता है । ध्यान दें कि प्रेषण समारोह है async, आप वहाँ अनुरोध कर सकते हैं (टिप्पणी)।
एस्टुस फ्लास्क

1
प्रश्न के साथ समस्या यह है कि आपने एक अलग चीज़ के बारे में पूछा जो कि आपका वास्तविक मामला नहीं था और फिर इसे बदल दिया, इसलिए अब यह बहुत अलग प्रश्न है, जबकि मौजूदा उत्तर ऐसे प्रतीत होते हैं मानो उन्होंने प्रश्न को अनदेखा कर दिया हो। यह SO पर एक अनुशंसित अभ्यास नहीं है क्योंकि यह आपको एक उत्तर प्राप्त करने में मदद नहीं करता है जो आप चाहते हैं। कृपया, उस प्रश्न से महत्वपूर्ण भागों को न निकालें जो उत्तर पहले से ही (गणना सूत्रों) को संदर्भित करता है। यदि आपको अभी भी समस्या है (मेरी पिछली टिप्पणी के बाद (आप संभावना करते हैं), तो एक नया सवाल पूछने पर विचार करें जो आपके मामले को दर्शाता है और इसे आपके पिछले प्रयास के रूप में लिंक करता है।
एस्टस फ्लास्क

हाय estus, मुझे लगता है कि यह कोडैंडबॉक्स codeandbox.io/s/6z4r79ymwr अभी अपडेट नहीं हुआ है। मैं सवाल को वापस बदल दूंगा, इसके लिए खेद है।
रिंझंग

3

Ref का प्रयोग ऐप में एक नए तरह के बग को पेश करेगा।

आइए इस मामले को देखें usePreviousकि किसी ने पहले टिप्पणी की थी:

  1. prop.minTime: 5 ==> ref.current = 5 | ref.current सेट करें
  2. prop.minTime: 5 ==> ref.current = 5 | नया मान ref.current के बराबर है
  3. prop.minTime: 8 ==> ref.current = 5 | नया मान ref.current के बराबर नहीं है
  4. prop.minTime: 5 ==> ref.current = 5 | नया मान ref.current के बराबर है

जैसा कि हम यहां देख सकते हैं, हम आंतरिक का अद्यतन नहीं refकर रहे हैं क्योंकि हम उपयोग कर रहे हैंuseEffect


1
रिएक्ट का यह उदाहरण है, लेकिन वे state... का उपयोग कर रहे हैं ... नहीं props... जब आप पुराने प्रॉप्स की परवाह करते हैं तो त्रुटि हो जाएगी।
santomegonzalo

3

वास्तव में सरल प्रोप तुलना के लिए आप आसानी से उपयोग कर सकते हैं कि अगर कोई प्रोपेक्ट अपडेट हुआ है तो यह देखने के लिए जांच करें।

const myComponent = ({ prop }) => {
  useEffect(() => {
     ---Do stuffhere----
    }
  }, [prop])

}

यूज़फेक तब केवल अपना कोड चलाएगा जब प्रोप बदल जाएगा।


1
यह केवल एक बार काम करता है, लगातार propपरिवर्तनों के बाद आप नहीं कर पाएंगे~ do stuff here ~
करोलिस narapnickis

2
लगता है कि आपको अपने राज्य को "रीसेट" करने setHasPropChanged(false)के अंत में कॉल करना चाहिए ~ do stuff here ~। (लेकिन यह एक अतिरिक्त रेंडरर में रीसेट हो जाएगा)
केविन वांग

प्रतिक्रिया के लिए धन्यवाद, आप दोनों सही हैं, अद्यतन समाधान
चार्ली टुपमैन

@ एंटोनियोपैविसेवैक-ऑर्टिज़ मैंने अब उत्तर को अपडेट करने का प्रस्ताव अपडेट किया है जो सच के रूप में बदल दिया गया है जो इसे एक बार प्रस्तुत करने के लिए कहेंगे, उपयोग करने के लिए चीरने के लिए एक बेहतर समाधान हो सकता है और बस प्रस्ताव की जांच कर सकते हैं
चार्ली ट्यूपमैन

मुझे लगता है कि इसका मूल उपयोग खो गया है। कोड को देखते हुए आप सिर्फ उपयोग कर सकते हैं
चार्ली टुपमैन

2

स्वीकार किए गए उत्तर को बंद करके, एक वैकल्पिक समाधान जिसे कस्टम हुक की आवश्यकता नहीं है:

const Component = (props) => {
  const { receiveAmount, sendAmount } = props;
  const prevAmount = useRef({ receiveAmount, sendAmount }).current;
  useEffect(() => {
    if (prevAmount.receiveAmount !== receiveAmount) {
     // process here
    }
    if (prevAmount.sendAmount !== sendAmount) {
     // process here
    }
    return () => { 
      prevAmount.receiveAmount = receiveAmount;
      prevAmount.sendAmount = sendAmount;
    };
  }, [receiveAmount, sendAmount]);
};

1
अच्छा समाधान है, लेकिन इसमें सिंटैक्स त्रुटियां हैं। useRefनहीं है userRef। वर्तमान का उपयोग करना भूल गएprevAmount.current
ब्लेक प्लम्ब

0

यहां एक कस्टम हुक है जो मैं उपयोग करता हूं जो मेरा मानना ​​है कि उपयोग करने से अधिक सहज है usePrevious

import { useRef, useEffect } from 'react'

// useTransition :: Array a => (a -> Void, a) -> Void
//                              |_______|  |
//                                  |      |
//                              callback  deps
//
// The useTransition hook is similar to the useEffect hook. It requires
// a callback function and an array of dependencies. Unlike the useEffect
// hook, the callback function is only called when the dependencies change.
// Hence, it's not called when the component mounts because there is no change
// in the dependencies. The callback function is supplied the previous array of
// dependencies which it can use to perform transition-based effects.
const useTransition = (callback, deps) => {
  const func = useRef(null)

  useEffect(() => {
    func.current = callback
  }, [callback])

  const args = useRef(null)

  useEffect(() => {
    if (args.current !== null) func.current(...args.current)
    args.current = deps
  }, deps)
}

आप useTransitionनिम्नानुसार उपयोग करेंगे।

useTransition((prevRate, prevSendAmount, prevReceiveAmount) => {
  if (sendAmount !== prevSendAmount || rate !== prevRate && sendAmount > 0) {
    const newReceiveAmount = sendAmount * rate
    // do something
  } else {
    const newSendAmount = receiveAmount / rate
    // do something
  }
}, [rate, sendAmount, receiveAmount])

उम्मीद है की वो मदद करदे।

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