React Hooks useEffect üzerindeki eski ve yeni Değerler nasıl karşılaştırılır?


151

Diyelim ki 3 girişim var: rate, sendAmount ve receAmount. Bu 3 girişi useEffect difing params üzerine koydum. Kurallar:

  • SendAmount değiştiyse, hesaplıyorum receiveAmount = sendAmount * rate
  • ReceAmount değiştiyse, hesaplıyorum sendAmount = receiveAmount / rate
  • Oran değiştiğinde receiveAmount = sendAmount * ratene zaman hesaplıyorum sendAmount > 0ya da sendAmount = receiveAmount / ratene zaman hesaplıyorumreceiveAmount > 0

Sorunu göstermek için kod ve https://codesandbox.io/s/pkl6vn7x6j kodları .

Bu durumda 3 işleyici yapmak yerine oldValuesve newValuesbenzerlerini karşılaştırmanın bir yolu var mı componentDidUpdate?

Teşekkürler


İşte https://codesandbox.io/s/30n01w2r06 ile son çözümümusePrevious

Bu durumda, useEffecther değişiklik aynı ağ çağrısına yol açtığı için çoklu kullanamıyorum . Bu yüzden changeCountdeğişikliği izlemek için de kullanıyorum . Bu changeCountaynı zamanda yalnızca yerel değişikliklerin izlenmesine yardımcı olur, böylece sunucudaki değişiklikler nedeniyle gereksiz ağ çağrısını önleyebilirim.


ComponentDidUpdate tam olarak nasıl yardımcı olur? Yine de bu 3 koşulu yazmanız gerekecektir.
Estus Flask

2 isteğe bağlı çözümle bir cevap ekledim. Bunlardan biri sizin için çalışıyor mu?
Ben Carp

Yanıtlar:


211

UseRef kullanarak önceki bir sahne sağlamak için özel bir kanca yazabilirsiniz

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

ve sonra içinde kullan 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])
}

Bununla birlikte, useEffectayrı olarak işlemek istediğiniz her değişiklik kimliği için iki ayrı ayrı kullanılıp kullanılmadığını okumak ve anlamak daha açık ve muhtemelen daha iyi ve daha açıktır.


8
İki useEffectçağrıyı ayrı ayrı kullanma notu için teşekkür ederiz . Birden fazla kez kullanabileceğinizi bilmiyordum!
JasonH

3
Yukarıdaki kodu denedim, ama eslint useEffecteksik bağımlılıklar beni uyarıyorprevAmount
Littlee

2
PrevAmount, önceki durum / sahne değeri için değer tuttuğundan, UseEffect işlevini kullanmak için bağımlı olarak geçirmeniz gerekmez ve bu uyarıyı belirli bir durum için devre dışı bırakabilirsiniz. Daha fazla bilgi için bu gönderiyi okuyabilir misiniz?
Shubham Khatri

Bunu sev - useRef her zaman kurtarmaya gelir.
Fernando Rojo

@ShubhamKhatri: ref.current = değerinin useEffect kaydırmasının nedeni nedir?
curly_brackets

41

Herkesin bir TypeScript kullanım sürümü araması durumunda

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
Bunun bir TSX dosyasında işe yaramayacağını, bunun bir TSX dosya değişikliğinde çalışmasını const usePrevious = <T extends any>(...sağlamak için yorumlayıcının <T> JSX olmadığını ve genel bir kısıtlama olduğunu
düşünmesini sağlayın

1
Neden <T extends {}>işe yaramayacağını açıklayabilir misiniz? Benim için çalışıyor gibi görünüyor ama bunu böyle kullanmanın karmaşıklığını anlamaya çalışıyorum.
Karthikeyan_kk

.tsxdosyaları html ile aynı olması gereken jsx içerir. Jenerik <T>kapatılmamış bir bileşen etiketi olabilir.
SeedyROM

Dönüş türü ne olacak? AnladımMissing return type on function.eslint(@typescript-eslint/explicit-function-return-type)
Saba Ahang

@SabaAhang Üzgünüm! TS, çoğunlukla dönüş türlerini işlemek için tür çıkarımı kullanacaktır, ancak linting'i etkinleştirdiyseniz, uyarılardan kurtulmak için bir dönüş türü ekledim.
SeedyROM

25

Seçenek 1 - KullanımKanca Kancası

Geçerli bir değeri önceki bir değerle karşılaştırmak yaygın bir kalıptır ve uygulama ayrıntılarını gizleyen kendi özel kancasını haklı çıkarır.

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>;
};

gösteri

Seçenek 2 - Değer değiştiğinde useEffect'i çalıştırın

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

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

gösteri


İkinci seçenek benim için çalıştı. UseEffect Twice neden yazmam gerektiğini bana yol gösterebilir misiniz?
Tarun Nagpal

@TarunNagpal, mutlaka useEffect'i iki kez kullanmanız gerekmez. Kullanım durumunuza bağlıdır. Sadece hangi valin değiştiğini kaydetmek istediğimizi düşünün. Bağımlılık dizimizde hem val1 hem de val2 varsa, değerlerden her değiştiğinde çalışır. Daha sonra geçirdiğimiz fonksiyonun içinde, doğru mesajı kaydetmek için hangi valin değiştiğini bulmamız gerekecek.
Ben Carp

Cevap için teşekkürler. "Geçtiğimiz fonksiyonun içinde hangi val'in değiştiğini bulmalıyız" dediğinde. Nasıl başarabiliriz. Lütfen rehber
Tarun Nagpal

6

Az önce bu tür bir senaryoyu çözen tepki-delta yayınladım . Bence,useEffect çok fazla sorumluluğu var.

Sorumluluklar

  1. Bağımlılık dizisindeki tüm değerleri kullanarak karşılaştırır. Object.is
  2. # 1 sonucuna göre efekt / temizleme geri çağrıları çalıştırır

Sorumlulukların Ayrılması

react-deltasonları useEffectbirkaç küçük kanca içine 'ın sorumlulukları.

Sorumluluk # 1

Sorumluluk # 2

Deneyimlerime göre, bu yaklaşım useEffect/ useRefçözümlerden daha esnek, temiz ve özlüdür .


Tam aradığım şey. Deneyeceğim!
hackerl33t

gerçekten iyi görünüyor ama biraz abartılı değil mi?
Hisato

@Hisato çok iyi olabilir. Deneysel bir API. Ve açıkçası takımlarımda çok fazla kullanmadım çünkü yaygın olarak bilinmiyor ya da benimsenmiyor. Teoride kulağa hoş geliyor, ama pratikte buna değmeyebilir.
Austin Malerba

3

Durum, fonksiyonel bileşenlerde bileşen örneği ile sıkı bir şekilde eşleşmediğinden, önce duruma useEffectkaydetmeden önceki duruma erişilemez useRef. Bu aynı zamanda durum güncellemesinin yanlış yerde yanlış bir şekilde uygulandığı anlamına gelir, çünkü önceki durum içeride kullanılabilirsetState güncelleyici işlevi .

Bu, useReducer Redux benzeri bir mağaza sağlayan ve ilgili kalıbın uygulanmasına izin veren . Durum güncellemeleri açıkça gerçekleştirilir, bu nedenle hangi durum özelliğinin güncellendiğini anlamaya gerek yoktur; bu zaten gönderilen işlemden açıktır.

İşte neye benzeyebileceğine dair bir örnek :

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
});
...

Merhaba @estus, bu fikir için teşekkürler. Bana başka bir düşünme şekli veriyor. Ancak, her bir durum için API'yi aramam gerektiğini belirtmeyi unuttum. Bunun için bir çözümün var mı?
rwinzhang

Ne demek istiyorsun? İç tutamakDeğiştir? 'API' uzak API bitiş noktası anlamına mı geliyor? Kodları ve kutuyu yanıttan ayrıntılarla güncelleyebilir misiniz?
Estus Flask

Güncellemeden sendAmounthesaplamak yerine, vb. Zaman uyumsuz olarak getirmek istediğinizi varsayabilirim , değil mi? Bu çok değişiyor. Bunu yapmak mümkündür, useReduceancak zor olabilir. Daha önce Redux ile uğraştıysanız, zaman uyumsuz işlemlerin orada basit olmadığını biliyor olabilirsiniz. github.com/reduxjs/redux-thunk Redux için zaman uyumsuz işlemlere izin veren popüler bir uzantıdır. İşte aynı kalıplı redüktör (useThunkReducer), codesandbox.io/s/6z4r79ymwr . Gönderilen işlevin olduğuna dikkat edin, asyncorada istekte bulunabilirsiniz (yorumladı).
Estus Flask

1
Soru ile ilgili sorun, gerçek durumunuz olmayan farklı bir şey sorup sonra değiştirmiş olmanızdır, bu yüzden mevcut cevaplar soruyu görmezden geliyormuş gibi görünürken şimdi çok farklı bir soru. Bu SO üzerinde önerilen bir uygulama değildir, çünkü istediğiniz cevabı almanıza yardımcı olmaz. Lütfen, cevapların zaten başvurduğu sorudan (hesaplama formülleri) önemli parçaları çıkarmayın. Hala sorun yaşıyorsanız (önceki
yorumumdan

Merhaba estus, bence bu codesandbox codesandbox.io/s/6z4r79ymwr henüz güncellenmedi. Soruyu değiştireceğim, bunun için üzgünüm.
rwinzhang

3

Ref kullanmak, uygulamaya yeni bir tür hata tanıtacaktır.

usePreviousBirinin daha önce yorumladığı bu davayı görelim :

  1. prop.minTime: 5 ==> ref.current = 5 | ref. akım ayarı
  2. prop.minTime: 5 ==> ref.current = 5 | yeni değer ref.current değerine eşittir
  3. prop.minTime: 8 ==> ref.current = 5 | yeni değer ref. akım değerine eşit DEĞİLDİR
  4. prop.minTime: 5 ==> ref.current = 5 | yeni değer ref.current değerine eşittir

Burada gördüğümüz gibi, biz iç güncellenmiyor refkullandığımız çünküuseEffect


1
React'in bu örneği var ama eski propsları önemsediğinizde ... statedeğil ... kullanıyorlarsa propshata oluşacak.
santomegonzalo

3

Gerçekten basit prop karşılaştırması için, bir propin güncellenip güncellenmediğini kolayca kontrol etmek için useEffect'i kullanabilirsiniz.

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

}

useEffect kodunuzu yalnızca pervane değişirse çalıştırır.


1
Bu sadece bir kez çalışır, art arda yapılan propdeğişikliklerden sonra yapamazsınız~ do stuff here ~
Karolis Šarapnickis

2
Durumunuzu "sıfırlamak" için setHasPropChanged(false)sonunda aramalısınız gibi görünüyor ~ do stuff here ~. (Ama bu ekstra bir tekrarlayıcıda sıfırlanacaktı)
Kevin Wang

Geri bildiriminiz için teşekkürler, ikiniz de doğru, güncellenmiş çözüm
Charlie Tupman

@ AntonioPavicevac-Ortiz Şimdi propHasChanged'i doğru hale getirmek için cevabı güncelledim, bu daha sonra render üzerinde bir kez çağırır, sadece useEffect'i çıkarmak ve sadece pervaneyi kontrol etmek için daha iyi bir çözüm olabilir
Charlie Tupman

Sanırım bunu ilk kullanımım kayboldu. Koda geri baktığınızda sadece useEffect'i kullanabilirsiniz
Charlie Tupman

2

Özel bir kanca gerektirmeyen alternatif bir çözüm olan kabul edilen cevabın kapatılması:

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
İyi bir çözüm, ancak sözdizimi hataları içeriyor. useRefdeğil userRef. Akım kullanmayı unuttumprevAmount.current
Blake Plumb

0

İşte kullandığımdan daha sezgisel olduğuna inandığım özel bir kanca 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)
}

useTransitionAşağıdaki gibi kullanırsınız .

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])

Umarım yardımcı olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.