useEffect - Durumu güncellerken sonsuz döngüyü önler


9

Kullanıcının yapılacak iş öğelerinin listesini sıralayabilmesini istiyorum. Kullanıcılar bir açılır menüden bir öğe seçtiğinde, öğenin sortKeyyeni bir sürümünü oluşturacak setSortedTodosve ardından useEffectve çağrısını tetikleyecektir setSortedTodos.

Örneğin aşağıda, tam istediğim nasıl çalışır ancak eslint eklemek için beni kışkırtan, todoshiç useEffectdependancy dizisi ve bunu yaparsam (beklediğiniz gibi) o sonsuz döngüye neden olur.

const [todos, setTodos] = useState([]);
const [sortKey, setSortKey] = useState('title');

const setSortedTodos = useCallback((data) => {
  const cloned = data.slice(0);

  const sorted = cloned.sort((a, b) => {
    const v1 = a[sortKey].toLowerCase();
    const v2 = b[sortKey].toLowerCase();

    if (v1 < v2) {
      return -1;
    }

    if (v1 > v2) {
      return 1;
    }

    return 0;
  });

  setTodos(sorted);
}, [sortKey]);

useEffect(() => {
    setSortedTodos(todos);
}, [setSortedTodos]);

Canlı Örnek:

Ben bunu mutlu etmenizi sağlayan daha iyi bir yol olması gerektiğini düşünüyorum.


1
Sadece bir yan not: Geri sortarama sadece olabilir: return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());aynı zamanda ortamda makul yerel bilgi varsa yerel bir karşılaştırma yapma avantajına sahiptir. İsterseniz, ona da yıkım atabilirsiniz: pastebin.com/7X4M1XTH
TJ Crowder

Hangi hata eslintatıyor?
Luze

Yığın Parçacıkları'nı ( araç çubuğu düğmesi) kullanarak sorunun çalıştırılabilir bir minimum tekrarlanabilir örneği sağlamak için soruyu güncelleyebilir misiniz [<>]? Yığın Parçacıkları, JSX dahil React'i destekler; nasıl yapılacağı aşağıda açıklanmıştır . Bu şekilde insanlar önerilen çözümlerinin sonsuz döngü problemi olmadığını kontrol edebilirler ...
TJ Crowder

Bu gerçekten ilginç bir yaklaşım ve gerçekten ilginç bir problem. Söylediğiniz gibi, ESLint'in todosbağımlılık dizisine neden eklemeniz gerektiğini düşündüğünü anlayabilir useEffectve neden yapmamanız gerektiğini görebilirsiniz. :-)
TJ Crowder

Senin için canlı örneği ekledim. Gerçekten bunun cevaplandığını görmek istiyorum.
TJ Crowder

Yanıtlar:


8

Bunun bu şekilde devam etmenin ideal olmadığı anlamına geldiğini iddia ediyorum. Fonksiyon gerçekten bağımlıdır todos. Başka setTodosbir yere çağrılırsa, geri çağırma işlevinin yeniden hesaplanması gerekir, aksi takdirde eski veriler üzerinde çalışır.

Sıralı diziyi neden yine de durumda saklıyorsunuz? useMemoAnahtar veya dizi değiştiğinde değerleri sıralamak için kullanabilirsiniz :

const sortedTodos = useMemo(() => {
  return Array.from(todos).sort((a, b) => {
    const v1 = a[sortKey].toLowerCase();
    const v2 = b[sortKey].toLowerCase();

    if (v1 < v2) {
      return -1;
    }

    if (v1 > v2) {
      return 1;
    }

    return 0;
  });
}, [sortKey, todos]);

Sonra sortedTodosher yere başvurun .

Canlı Örnek:

Sıralanan diziyi her zaman "temel" diziden ve sıralama anahtarından türetebildiğiniz / hesaplayabildiğiniz için, sıralanan değerleri durumda depolamaya gerek yoktur. Daha az karmaşık olduğu için kodunuzun anlaşılmasını kolaylaştırdığını iddia ediyorum.


Oh güzel kullanımı useMemo. Sadece bir yan soru, neden .localComparesıralamada kullanmıyorsunuz ?
Tikkes

2
Gerçekten daha iyi olurdu. Sadece OP kodunu kopyaladım ve buna dikkat etmedim (problemle gerçekten ilgili değil).
Felix Kling

Gerçekten basit, anlaşılması kolay bir çözüm.
TJ Crowder

Ah evet, kullan! Bunu unuttum :)
DanV

3

Sonsuz döngünün nedeni, todos'un önceki referansla eşleşmemesi ve efektin yeniden çalıştırılmasıdır.

Neden yine de bir tıklama işlemi için efekt kullanıyorsunuz? Bunu aşağıdaki gibi bir işlevde çalıştırabilirsiniz:

const [todos, setTodos] = useState([]);

function sortTodos(e) {
    const sortKey = e.target.value;
    const clonedTodos = [...todos];
    const sorted = clonedTodos.sort((a, b) => {
        return a[sortKey.toLowerCase()].localeCompare(b[sortKey.toLowerCase()]);
    });

    setTodos(sorted);
}

ve açılır menüde bir onChange.

    <select onChange="sortTodos"> ......

Bu arada bağımlılığa dikkat edin, ESLint haklıdır! Todo'larınız, yukarıda açıklanan durumda, bir bağımlılıktır ve listede olmalıdır. Bir öğenin seçimine ilişkin yaklaşım yanlıştır ve dolayısıyla probleminiz.


2
"sort dizinin yeni bir örneğini döndürür" Yerleşik sıralama yöntemi değil. Diziyi yerinde sıralar. data.slice(0)kopyayı oluşturur.
Felix Kling

Bunu yaptığınızda bu doğru değildir, setStateçünkü mevcut nesneyi düzenlemeyecektir ve bunun için dahili olarak klonlayacaktır. Cevabımda yanlış ifadeler, doğru. Bunu düzenleyeceğim.
Tikkes

setStateverileri klonlamaz. Neden öyle düşünüyorsun?
Felix Kling

1
@Tikkes - Hayır, setStatehiçbir şeyi klonlamıyor . Felix ve OP doğrudur, diziyi sıralamadan önce kopyalamanız gerekir.
TJ Crowder

Tamam evet, üzgünüm. İçeriden biraz daha okumaya ihtiyacım var gibi görünüyor. Gerçekten var olanı kopyalamalı ve değiştirmemelisiniz state.
Tikkes

0

Burada yapmanız gereken şey fonksiyonel formunu kullanmaktır setState:

  const [todos, setTodos] = useState(exampleToDos);
    const [sortKey, setSortKey] = useState('title');

    const setSortedTodos = useCallback((data) => {

      setTodos(currTodos => {
        return currTodos.sort((a, b) => {
          const v1 = a[sortKey].toLowerCase();
          const v2 = b[sortKey].toLowerCase();

          if (v1 < v2) {
            return -1;
          }

          if (v1 > v2) {
            return 1;
          }

          return 0;
        });
      })

    }, [sortKey]);

    useEffect(() => {
        setSortedTodos(todos);
    }, [setSortedTodos, todos]);

Çalışma kodları ve kutusu

Orijinali mutasyona uğratmamak için durumu kopyalasanız bile, durumun zaman uyumsuz olması nedeniyle en son değerini alacağınız hala garanti edilmez. Ayrıca, yöntemlerin çoğu sığ bir kopya döndürür, böylece orijinal durumu yine de mutasyona uğratabilirsiniz.

İşlevsel kullanımı setState, durumun en son değerini almanızı ve orijinal durum değerini değiştirmemenizi sağlar.


"ve orijinal durum değerini değiştirmeyin." React'ın bir kopyasını geri aramaya aktardığını sanmıyorum . .sortdiziyi yerinde değiştirir, böylece yine de kendiniz kopyalamanız gerekir.
Felix Kling

Hmm, iyi bir nokta, aslında devletin kopyasını geçtiğine dair bir onay bulamıyorum, daha önce böyle bir şey okuduğumu hatırlıyorum.
Clarity

Burada buldum : reaktjs.org/docs/… . 'setState'in fonksiyonel güncelleme formunu kullanabiliriz. Mevcut durumu referans almadan devletin nasıl değişmesi gerektiğini belirlememize izin veriyor ''
Clarity

Bunu bir kopya olarak almýyorum. Bu sadece mevcut durumu "harici" bağımlılık olarak sağlamanız gerekmediği anlamına gelir.
Felix Kling
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.