Sonsuz döngü kullanımda


106

React 16.7-alpha'daki yeni kanca sistemi ile oynuyordum ve kullandığım durum bir nesne veya dizi olduğunda kullanımda sonsuz bir döngüde sıkışıp kalıyorumEffect.

İlk önce useState'i kullanıyorum ve şunun gibi boş bir nesneyle başlatıyorum:

const [obj, setObj] = useState({});

Daha sonra useEffect'te setObj'yi tekrar boş bir nesneye ayarlamak için kullanıyorum. İkinci bir argüman olarak, nesnenin içeriği değişmediyse güncellenmeyeceğini umarak [obj] 'i geçiyorum . Ancak güncellenmeye devam ediyor. Sanırım içerik ne olursa olsun, bunlar her zaman farklı nesnelerdir ve React'in değişmeye devam ettiğini düşündürür.

useEffect(() => {
  setIngredients({});
}, [ingredients]);

Aynısı diziler için de geçerlidir, ancak bir ilkel olarak beklendiği gibi bir döngüde sıkışıp kalmayacaktır.

Bu yeni kancaları kullanarak, içeriğin değişip değişmediğini kontrol ederken nesneleri ve diziyi nasıl kullanmalıyım?


Tobias, hangi kullanım durumu, değeri değiştiğinde, inkarcıların değerini değiştirmeyi gerektirir?
Ben Carp

@Tobias, cevabımı okumalısın. Eminim doğru cevap olarak kabul edeceksiniz.
HalfWebDev

Yanıtlar:


125

UseEffect'e ikinci argüman olarak boş bir dizi geçirmek, dizinin yalnızca mount ve unmount üzerinde çalışmasını sağlar ve böylece sonsuz döngüleri durdurur.

useEffect(() => {
  setIngredients({});
}, []);

Bu, https://www.robinwieruch.de/react-hooks/ adresindeki React kancaları hakkındaki blog gönderisinde bana açıklandı.


1
Aslında boş bir dizi, geçmeniz gereken boş bir nesne değil.
GifCo

17
Bu sorunu çözmez, kanca tarafından kullanılan bağımlılıkları geçmeniz gerekir
helado

1
Unmount'ta, belirttiyseniz efektin bir temizleme işlevi çalıştıracağını unutmayın. Asıl etki, ayrıldığında çalışmaz. reactjs.org/docs/hooks-effect.html#example-using-hooks-1
tony

2
SetIngrediets bir bağımlılık olduğunda boş dizi kullanmak, Dan Abramov'un dediği gibi bir anti-modeldir. UseEffetct'e bir componentDidMount () yöntemi gibi davranmayın
p7adams

1
Yukarıdaki kişiler bu sorunun nasıl doğru bir şekilde çözülebileceğine dair bir bağlantı veya kaynak sağlamadığından, sonsuz döngü sorunumu çözdüğü için yaklaşmakta olan ördek yavrusu ziyaretçilere bunu kontrol etmelerini öneririm: stackoverflow.com/questions/56657907/…
C.

72

Aynı sorunu yaşadım. Bundan neden belgelerde bahsetmediklerini bilmiyorum. Tobias Haugen cevabına biraz eklemek istiyorum.

Her bileşende / üst yeniden oluşturucuda çalıştırmak için şunları kullanmanız gerekir:

  useEffect(() => {

    // don't know where it can be used :/
  })

Herhangi bir şeyi bileşen bağladıktan sonra yalnızca bir kez çalıştırmak için (bir kez oluşturulacaktır) kullanmanız gerekir:

  useEffect(() => {

    // do anything only one time if you pass empty array []
    // keep in mind, that component will be rendered one time (with default values) before we get here
  }, [] )

Bileşen bağlama ve veri / veri2 değişikliği üzerinde bir kez herhangi bir şeyi çalıştırmak için :

  const [data, setData] = useState(false)
  const [data2, setData2] = useState('default value for first render')
  useEffect(() => {

// if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed
// if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone).
// if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this)
  }, [data, data2] )

Çoğu zaman nasıl kullanırım:

export default function Book({id}) { 
  const [book, bookSet] = useState(false) 

  useEffect(() => {
    loadBookFromServer()
  }, [id]) // every time id changed, new book will be loaded

  // Remeber that it's not always safe to omit functions from the list of dependencies. Explained in comments.
  async function loadBookFromServer() {
    let response = await fetch('api/book/' + id)
    response  = await response.json() 
    bookSet(response)
  }

  if (!book) return false //first render, when useEffect did't triggered yet we will return false

  return <div>{JSON.stringify(book)}</div>  
}


@endavid, kullandığınız pervanenin ne olduğuna bağlıdır
ZiiMakc

1
elbette, bileşen kapsamından herhangi bir değer kullanmazsanız , atlamak güvenlidir. Ancak, tamamen tasarım / mimarlık açısından bakıldığında, bu iyi bir uygulama değildir, çünkü sahne donanımı kullanmanız gerekiyorsa tüm işlevinizi efektin içine taşımanızı gerektirir ve bir useEffect yöntemiyle sonuçlanabilirsiniz. aşırı miktarda kod satırı.
egdavid

16

Ben de aynı problemle bir kez karşılaştım ve ikinci argümanda ilkel değerleri aktardığımdan emin olarak sorunu çözdüm [].

Bir nesneyi iletirseniz, React yalnızca nesneye olan referansı saklar ve referans değiştiğinde etkiyi çalıştırır, ki bu genellikle her tek seferdir (şimdi nasıl yapacağımı bilmiyorum).

Çözüm, nesnedeki değerleri iletmektir. Deneyebilirsin,

const obj = { keyA: 'a', keyB: 'b' }

useEffect(() => {
  // do something
}, [Object.values(obj)]);

veya

const obj = { keyA: 'a', keyB: 'b' }

useEffect(() => {
  // do something
}, [obj.keyA, obj.keyB]);

3
Başka bir yaklaşım, useMemo ile bu tür değerler oluşturmaktır, bu şekilde referans korunur ve bağımlılıkların değerleri aynı olarak değerlendirilir
helado

@helado değerler için useMemo () kullanabilir veya işlevler içinCallback () kullanabilirsiniz
Juanma Menendez

11

Belgelerde ( https://reactjs.org/docs/hooks-effect.html ) belirtildiği gibi, useEffectkanca, her işlemeden sonra bir kodun çalıştırılmasını istediğinizde kullanılmak üzere tasarlanmıştır . Dokümanlardan:

UseEffect her işlemeden sonra çalışır mı? Evet!

Bunu özelleştirmek isterseniz, aynı sayfada daha sonra görünen talimatları takip edebilirsiniz ( https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects ). Temel olarak, useEffectyöntem , React'in etkinin tekrar tetiklenmesi gerekip gerekmediğini belirlemek için inceleyeceği ikinci bir argümanı kabul eder .

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

Herhangi bir nesneyi ikinci argüman olarak iletebilirsiniz. Bu nesne değişmeden kalırsa, etkiniz yalnızca ilk binekten sonra tetiklenecektir . Nesne değişirse, efekt yeniden tetiklenecektir.


11

Özel bir kanca oluşturuyorsanız , bazen aşağıdaki gibi varsayılan olarak sonsuz bir döngüye neden olabilirsiniz

function useMyBadHook(values = {}) {
    useEffect(()=> { 
           /* This runs every render, if values is undefined */
        },
        [values] 
    )
}

Düzeltme, her işlev çağrısında yeni bir tane oluşturmak yerine aynı nesneyi kullanmaktır:

const defaultValues = {};
function useMyBadHook(values = defaultValues) {
    useEffect(()=> { 
           /* This runs on first call and when values change */
        },
        [values] 
    )
}

Bileşen kodunuzda bununla karşılaşırsanız, ES6 varsayılan değerleri yerine defaultProps kullanırsanız döngü düzeltilebilir.

function MyComponent({values}) {
  useEffect(()=> { 
       /* do stuff*/
    },[values] 
  )
  return null; /* stuff */
}

MyComponent.defaultProps = {
  values = {}
}

Teşekkürler! Bu benim için açık değildi ve tam olarak karşılaştığım sorun buydu.
22'de sıkışmış

1
Günümü kurtardın! Sağ ol, kanka.
Han Van Pham

6

Bunun işinize yarayıp yaramayacağından emin değilim ama .length eklemeyi şu şekilde deneyebilirsiniz:

useEffect(() => {
        // fetch from server and set as obj
}, [obj.length]);

Benim durumumda (bir dizi alıyordum!), Verileri bağlarken aldı, sonra yine yalnızca değişimde ve bir döngüye girmedi.


1
Dizideki bir öğe değiştirilirse ne olur? Bu durumda dizinin uzunluğu aynı olur ve etki çalışmaz!
Aamir Khan

6

UseEffect'in sonuna boş dizi eklerseniz :

useEffect(()=>{
        setText(text);
},[])

Bir kez çalışır.

Dizi üzerine parametre de eklerseniz:

useEffect(()=>{
            setText(text);
},[text])

Metin parametresi her değiştiğinde çalışır.


kancanın sonuna boş bir dizi koyarsak neden yalnızca bir kez çalışsın?
Karen

UseEffect'in sonundaki boş bir dizi, örneğin bir useEffect'in içinde setState'i ayarlamanız gereken durumlarda, geliştiriciler tarafından sonsuz döngüleri durdurmak için amaca yönelik bir uygulamadır. Aksi takdirde bu, useEffect -> state update -> useEffect -> sonsuz döngüye yol açar.
to240

4

Sonsuz döngünüz döngüsellikten kaynaklanıyor

useEffect(() => {
  setIngredients({});
}, [ingredients]);

setIngredients({});çalışacak olan ingredients(her seferinde yeni bir referans döndürür) değerini değiştirir setIngredients({}). Bunu çözmek için her iki yaklaşımı da kullanabilirsiniz:

  1. UseEffect için farklı bir ikinci argüman iletin
const timeToChangeIngrediants = .....
useEffect(() => {
  setIngredients({});
}, [timeToChangeIngrediants ]);

setIngrediantstimeToChangeIngrediantsdeğiştiğinde çalışacak .

  1. Bir kez değiştirildikten sonra, değişim inkarcıları için hangi kullanım senaryosunun haklı olduğunu bilmiyorum Ancak durum buysa, Object.values(ingrediants)Effect'i kullanmak için ikinci bir argüman olarak geçersiniz.
useEffect(() => {
  setIngredients({});
}, Object.values(ingrediants));

2

Bu optimizasyonu kullanırsanız, dizinin bileşen kapsamındaki zamanla değişen ve efekt tarafından kullanılan tüm değerleri (props ve state gibi) içerdiğinden emin olun.

Eski verilerin kullanılması olasılığını ifade etmeye ve bunun farkında olmaya çalıştıklarına inanıyorum. arrayİkinci bağımsız değişken için gönderdiğimiz değerlerin türü önemli değil, bildiğimiz sürece bu değerlerden herhangi biri değişirse etkiyi çalıştıracaktır. ingredientsEfekt içinde hesaplamanın bir parçası olarak kullanıyorsak , bunu array.

const [ingredients, setIngredients] = useState({});

// This will be an infinite loop, because by shallow comparison ingredients !== {} 
useEffect(() => {
  setIngredients({});
}, [ingredients]);

// If we need to update ingredients then we need to manually confirm 
// that it is actually different by deep comparison.

useEffect(() => {
  if (is(<similar_object>, ingredients) {
    return;
  }
  setIngredients(<similar_object>);
}, [ingredients]);


1

En iyi şekilde kullanarak geçerli değeri ile önceki değerini karşılaştırmak için usePrevious () ve _.isEqual () gelen Lodash . İthalat IsEqual ve useRef . Önceki değerinizi useEffect () içindeki mevcut değerle karşılaştırın . Eğer aynıysa başka hiçbir şey güncelleme yapmayın. usePrevious (değer) , useRef () ile bir ref oluşturan özel bir kancadır .

Aşağıda kodumun pasajı var. Firebase kancasını kullanarak verileri güncellerken sonsuz döngü sorunu yaşıyordum

import React, { useState, useEffect, useRef } from 'react'
import 'firebase/database'
import { Redirect } from 'react-router-dom'
import { isEqual } from 'lodash'
import {
  useUserStatistics
} from '../../hooks/firebase-hooks'

export function TMDPage({ match, history, location }) {
  const usePrevious = value => {
    const ref = useRef()
    useEffect(() => {
      ref.current = value
    })
    return ref.current
  }
  const userId = match.params ? match.params.id : ''
  const teamId = location.state ? location.state.teamId : ''
  const [userStatistics] = useUserStatistics(userId, teamId)
  const previousUserStatistics = usePrevious(userStatistics)

  useEffect(() => {
      if (
        !isEqual(userStatistics, previousUserStatistics)
      ) {
        
        doSomething()
      }
     
  })


1
Sanırım üçüncü şahıs bir kitaplık kullanmayı önerdiğiniz için insanlar buna olumsuz oy verdi. Ancak temelde yatan fikir iyidir.
jperl

1

Nesneyi karşılaştırmanız GEREKİRSE ve burada güncellendiğinde deepComparekarşılaştırma için bir kanca var. Kabul edilen cevap kesinlikle buna değinmiyor. Bir []diziye sahip olmak , efektin monte edildiğinde yalnızca bir kez çalışması gerekiyorsa uygundur.

Ayrıca, oylanan diğer yanıtlar, yalnızca ilkel türler için yapılan bir denetimi obj.valueveya ilk önce iç içe olmadığı düzeye ulaşmaya benzer bir şeyi ele alır. Derinlemesine iç içe geçmiş nesneler için bu en iyi durum olmayabilir.

İşte her durumda işe yarayacak bir tane.

import { DependencyList } from "react";

const useDeepCompare = (
    value: DependencyList | undefined
): DependencyList | undefined => {
    const ref = useRef<DependencyList | undefined>();
    if (!isEqual(ref.current, value)) {
        ref.current = value;
    }
    return ref.current;
};

Aynısını useEffectkancada kullanabilirsiniz

React.useEffect(() => {
        setState(state);
    }, useDeepCompare([state]));

0

Ayrıca bağımlılık dizisindeki nesneyi de yok edebilirsiniz, yani durum yalnızca nesnenin belirli bölümleri güncellendiğinde güncellenir.

Bu örnek uğruna, diyelim ki malzemeler havuç içeriyordu, bunu bağımlılığa aktarabiliriz ve sadece havuç değiştiğinde durum güncellenirdi.

Daha sonra bunu daha da ileri götürebilir ve yalnızca belirli noktalarda havuç sayısını güncelleyebilir, böylece durumun ne zaman güncelleneceğini kontrol edebilir ve sonsuz döngüden kaçınabilirsiniz.

useEffect(() => {
  setIngredients({});
}, [ingredients.carrots]);

Bunun gibi bir şeyin ne zaman kullanılabileceğine bir örnek, bir kullanıcının bir web sitesinde oturum açmasıdır. Giriş yaptıklarında, çerez ve izin rollerini çıkarmak için kullanıcı nesnesini yok edebilir ve uygulamanın durumunu buna göre güncelleyebiliriz.


0

Benim Vakam sonsuz bir döngü ile karşılaşma konusunda özeldi, senaryo şöyleydi:

Bir Nesnem vardı, diyelim ki nesnelerden gelen objX ve onu aşağıdaki gibi sahne donanımlarında yok ediyordum:

const { something: { somePropery } } = ObjX

ve benzerlerime someProperybağımlılık olarak kullandım useEffect:


useEffect(() => {
  // ...
}, [somePropery])

ve bana sonsuz bir döngüye neden oldu, bütününü somethingbağımlılık olarak geçirerek bunu halletmeye çalıştım ve düzgün çalıştı.

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.