사용중인 무한 루프


115

React 16.7-alpha에서 새로운 후크 시스템을 가지고 놀았고 내가 처리중인 상태가 객체 또는 배열 일 때 useEffect에서 무한 루프에 갇혀 있습니다.

먼저 useState를 사용하고 다음과 같은 빈 개체로 시작합니다.

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

그런 다음 useEffect에서 setObj를 사용하여 다시 빈 개체로 설정합니다. 두 번째 인수로 객체 의 내용 이 변경되지 않은 경우 업데이트되지 않기를 바라면서 [obj]를 전달 합니다. 하지만 계속 업데이트됩니다. 내용에 관계없이 항상 다른 객체이기 때문에 React가 계속 변한다고 생각합니까?

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

배열의 경우도 마찬가지지만, 기본적으로 예상대로 루프에 갇히지 않습니다.

이러한 새로운 후크를 사용하여 콘텐츠가 변경되었는지 여부를 확인할 때 객체 및 배열을 어떻게 처리해야합니까?


Tobias, 값이 변경되면 Ingrediants의 값을 변경해야하는 사용 사례는 무엇입니까?
Ben Carp

@Tobias, 내 대답을 읽어야합니다. 나는 당신이 정답으로 받아 들일 것이라고 확신합니다.
HalfWebDev

답변:


128

빈 배열을 useEffect의 두 번째 인수로 전달하면 마운트 및 마운트 해제시에만 실행되므로 무한 루프가 중지됩니다.

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

이것은 https://www.robinwieruch.de/react-hooks/의 React hooks에 대한 블로그 게시물에서 저에게 명확 해졌습니다.


1
실제로 전달해야하는 빈 객체가 아닌 빈 배열입니다.
GifCo

20
이 문제가 해결되지 않습니다, 당신은 후크가 사용 종속성을 통과해야
helado

1
마운트 해제시 효과를 지정한 경우 정리 기능이 실행됩니다. 마운트 해제시 실제 효과는 실행되지 않습니다. reactjs.org/docs/hooks-effect.html#example-using-hooks-1
토니

2
setIngrediets가 종속성 일 때 빈 배열을 사용하는 것은 Dan Abramov가 말한 것처럼 반 패턴입니다. 방법) (A componentDidMount 좋아하지 치료 useEffetct를 수행
p7adams

1
위의 사람들은이 문제를 올바르게 해결하는 방법에 대한 링크 나 리소스를 제공하지 않았기 때문에 다가오는 duckduckgoers가 내 무한 루프 문제를 해결했기 때문에 이것을 확인하는 것이 좋습니다. stackoverflow.com/questions/56657907/…
C. Rib

78

같은 문제가있었습니다. 왜 그들이 문서에서 이것을 언급하지 않았는지 모르겠습니다. Tobias Haugen 답변에 약간을 추가하고 싶습니다.

모든 구성 요소 / 상위 rerender 에서 실행하려면 다음 을 사용해야합니다.

  useEffect(() => {

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

컴포넌트 마운트 후 한 번만 실행하려면 (한 번 렌더링 됨) 다음을 사용해야합니다.

  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
  }, [] )

구성 요소 마운트 및 데이터 / 데이터 2 변경 시 한 번만 실행하려면 :

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

대부분의 시간 사용 방법 :

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은 사용하는 소품이 무엇인지에 따라 달라집니다
ZiiMakc

1
사용하지 않는 경우 물론, 어떤 구성 요소 범위에서 값을, 그것은 생략 안전합니다. 하지만 순전히 vue의 디자인 / 아키텍처 관점에서 보면이 방법은 좋은 방법이 아닙니다. props를 사용해야하는 경우 전체 함수를 이펙트 내부로 이동해야하고 최종적으로 사용할 useEffect 메서드를 사용할 수 있기 때문입니다. 엄청난 양의 코드 라인.
egdavid

18

같은 문제가 한 번도 발생했고 두 번째 인수에 기본 값을 전달하여 문제를 해결했습니다 [].

객체를 전달하면 React는 객체에 대한 참조 만 저장하고 참조가 변경 될 때 효과를 실행합니다. 이는 일반적으로 매번 매번 발생합니다 (지금은 그렇지 않습니다).

해결책은 객체의 값을 전달하는 것입니다. 당신은 시도 할 수 있습니다,

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

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

또는

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

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

4
또 다른 접근 방식은 useMemo를 사용하여 이러한 값을 생성하는 것입니다. 그러면 참조가 유지되고 종속성 값이 동일하게 평가됩니다
helado

@helado 당신은 값이나 useCallback () 함수에 대한 useMemo ()를 사용할 수 있습니다
Juanma 메넨 데즈

임의의 값이 종속성 배열에 전달되고 있습니까? 물론 이것은 무한 루프를 방지하지만 이것이 권장됩니까? 0종속성 배열에도 전달할 수 있습니까?
thinkvantagedu

11

문서 ( https://reactjs.org/docs/hooks-effect.html ) 에서 말했듯 이 useEffect후크는 렌더링 할 때마다 일부 코드가 실행되기를 원할 때 사용 됩니다 . 문서에서 :

useEffect는 모든 렌더링 후에 실행됩니까? 예!

이를 사용자 정의하려면 나중에 동일한 페이지 ( https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects ) 에 표시되는 지침을 따를 수 있습니다 . 기본적으로이 useEffect메서드는 React가 효과를 다시 트리거해야하는지 여부를 결정하기 위해 조사 할 두 번째 인수를 받습니다 .

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

두 번째 인수로 모든 개체를 전달할 수 있습니다. 이 개체가 변경되지 않으면 효과는 첫 번째 탈것 이후에만 발동 됩니다. 개체가 변경되면 효과가 다시 트리거됩니다.


11

사용자 정의 후크를 구축하는 경우 다음과 같이 기본값으로 무한 루프가 발생할 수 있습니다.

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

수정 사항은 모든 함수 호출에서 새 객체를 만드는 대신 동일한 객체를 사용하는 것입니다.

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

구성 요소 코드에서이 문제가 발생 하면 ES6 기본값 대신 defaultProps를 사용하면 루프가 수정 될 수 있습니다.

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

MyComponent.defaultProps = {
  values = {}
}

감사! 이것은 나에게 분명하지 않았으며 정확히 내가 겪은 문제였습니다.
stuckj

1
당신은 내 하루를 구했습니다! 고마워, 형.
Han Van Pham

7

이것이 효과가 있는지 확실하지 않지만 다음과 같이 .length를 추가해 볼 수 있습니다.

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

제 경우에는 (배열을 가져 왔습니다!) 마운트시 데이터를 가져 왔고 변경시에만 데이터를 가져 왔고 루프에 들어 가지 않았습니다.


1
어레이에서 항목이 교체되면 어떻게됩니까? 이 경우 배열의 길이가 동일하고 효과가 실행되지 않습니다!
아미르 칸

7

useEffect 끝에 빈 배열을 포함하는 경우 :

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

한 번 실행됩니다.

배열에 매개 변수도 포함하는 경우 :

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

텍스트 매개 변수가 변경 될 때마다 실행됩니다.


후크 끝에 빈 배열을 넣으면 한 번만 실행되는 이유는 무엇입니까?
Karen

useEffect 끝에있는 빈 배열은 예를 들어 useEffect 내부에서 setState가 필요한 상황에서 무한 루프를 중지하기 위해 개발자가 의도적으로 구현 한 것입니다. 그렇지 않으면 useEffect-> 상태 업데이트-> useEffect-> 무한 루프가 발생합니다.
to240

빈 배열은 eslinter에서 경고를 발생시킵니다.
hmcclungiii

4

무한 루프는 순환 성 때문입니다.

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

setIngredients({});ingredients(매번 새로운 참조를 반환) 의 값을 변경하여 setIngredients({}). 이 문제를 해결하려면 다음 방법 중 하나를 사용할 수 있습니다.

  1. useEffect에 다른 두 번째 인수를 전달하십시오.
const timeToChangeIngrediants = .....
useEffect(() => {
  setIngredients({});
}, [timeToChangeIngrediants ]);

setIngrediantstimeToChangeIngrediants변경 되면 실행됩니다 .

  1. 일단 변경되면 어떤 사용 사례가 변경 ingrediants를 정당화하는지 잘 모르겠습니다. 그러나이 경우 Object.values(ingrediants)useEffect에 두 번째 인수로 전달합니다.
useEffect(() => {
  setIngredients({});
}, Object.values(ingrediants));

2

이 최적화를 사용하는 경우 시간에 따라 변경되고 효과에서 사용되는 구성 요소 범위 (예 : 소품 및 상태)의 모든 값이 배열에 포함되어 있는지 확인합니다.

나는 그들이 오래된 데이터를 사용할 가능성을 표현하고 이것을 인식하려고 노력하고 있다고 믿습니다. array두 번째 인수에 대해 보내는 값의 유형 은 해당 값 중 하나라도 변경되면 효과가 실행된다는 것을 아는 한 중요하지 않습니다. ingredients효과 내에서 계산의 일부로 사용 하는 경우 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

가장 좋은 방법은 Lodash의 usePrevious ()_.isEqual () 을 사용하여 이전 값과 현재 값을 비교 하는 입니다. isEqualuseRef를 가져 옵니다 . 이전 값을 useEffect () 내부의 현재 값과 비교하십시오 . 그들이 동일하다면 아무것도 업데이트하지 마십시오. usePrevious (값) 크리에이트 맞춤 훅이다 REF()를 useRef .

아래는 내 코드의 일부입니다. firebase hook을 사용하여 데이터를 업데이트하는 무한 루프 문제에 직면했습니다.

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
타사 라이브러리 사용을 제안했기 때문에 사람들이 반대 투표를 한 것 같습니다. 그러나 근본적인 아이디어는 좋습니다.
jperl

1

개체를 비교할 필요가 있고 여기에 업데이트 될 때 deepCompare비교를 위한 후크가 있습니다. 받아 들여진 대답은 확실히 그것을 다루지 않습니다. []마운트 할 때 효과를 한 번만 실행해야하는 경우 어레이 를 사용하는 것이 적합합니다.

또한 다른 투표 된 답변 obj.value은 중첩되지 않은 수준에 먼저 도달하는 것과 유사한 방식으로 기본 유형에 대한 검사 만 처리합니다 . 이것은 깊이 중첩 된 개체의 경우 최상의 경우가 아닐 수 있습니다.

그래서 여기 모든 경우에 작동하는 것이 있습니다.

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

동일한 인 useEffect훅을 사용할 수 있습니다.

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

0

또한 종속성 배열에서 개체의 구조를 해제 할 수 있습니다. 즉, 개체의 특정 부분이 업데이트 될 때만 상태가 업데이트됩니다.

이 예를 위해 재료에 당근이 포함되어 있다고 가정 해 봅시다.이를 종속성에 전달할 수 있으며 당근이 변경된 경우에만 상태가 업데이트됩니다.

그런 다음 더 나아가 특정 지점에서 당근 수만 업데이트하여 상태가 업데이트되는시기를 제어하고 무한 루프를 피할 수 있습니다.

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

이와 같은 것을 사용할 수있는 예는 사용자가 웹 사이트에 로그인 할 때입니다. 로그인 할 때 사용자 개체를 분해하여 쿠키 및 권한 역할을 추출하고 그에 따라 앱의 상태를 업데이트 할 수 있습니다.


0

내 케이스는 무한 루프를 만났을 때 특별했고, 시나리오는 다음과 같았습니다.

나는 Object가 있었고 props에서 나온 objX를 말하고 다음과 같은 props에서 그것을 분해했습니다.

const { something: { somePropery } } = ObjX

그리고 나는 someProperyuseEffect같은 것에 대한 의존성으로 사용했습니다 .


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

그리고 그것은 나에게 무한 루프를 일으켰고, 전체 something를 종속성으로 전달하여 이것을 처리하려고 시도 했으며 제대로 작동했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.