source

무한 루프 인 useEffect

itover 2023. 2. 11. 09:13
반응형

무한 루프 인 useEffect

React 16.7-alpha의 새로운 훅 시스템을 가지고 놀다가 useEffect에서 무한 루프 상태에 빠졌습니다.

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

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

그런 다음 useEffect에서 setObj를 사용하여 다시 빈 객체로 설정합니다.두 번째 인수로 오브젝트의 내용이 변경되지 않으면 갱신되지 않길 바라며 [obj]를 전달합니다.하지만 계속 업데이트 됩니다.내용에 관계없이 이것들은 항상 다른 오브젝트이기 때문에 React는 계속 변화하고 있다고 생각할까요?

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

어레이도 마찬가지입니다만, 프리미티브로서 예상대로 루프가 되는 일은 없습니다.

이러한 새로운 후크를 사용하여 콘텐츠의 날씨 변경 여부를 확인할 때 객체 및 어레이를 어떻게 처리해야 합니까?

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

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

이는 https://www.robinwieruch.de/react-hooks/의 React hooks 블로그 투고에서 명확하게 설명되었습니다.

같은 문제가 있었어.나는 왜 그들이 이것을 의사들에게 언급하지 않는지 모르겠다.Tobias Haugen의 대답에 조금 덧붙이고 싶습니다.

필요한 모든 구성 요소/부모 렌더에서 실행하려면:

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

컴포넌트 마운트 및 data/data2 변경 시 어떤 것도 한 번 실행하려면:

  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) 

  const loadBookFromServer = useCallback(async () => {
    let response = await fetch('api/book/' + id)
    response  = await response.json() 
    bookSet(response)
  }, [id]) // every time id changed, new book will be loaded

  useEffect(() => {
    loadBookFromServer()
  }, [loadBookFromServer]) // useEffect will run once and when id changes


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

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

도 한번 두 '아, 아, 아, 아', '아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아,[].

객체를 전달하면 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]);

커스텀 훅을 구축하는 경우 다음과 같이 디폴트로 무한 루프가 발생할 수 있습니다.

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 = {}
}

당신의 무한 루프는 원형성 때문입니다.

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

setIngredients({});이 변경됩니다.ingredients됩니다), (실행)(실행).setIngredients({}) 이하려면 ,할 수 있습니다.

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

setIngrediants실행하다timeToChangeIngrediants

  1. 일단 성분이 바뀌면 어떤 활용사례가 적절한지 잘 모르겠습니다. 그런이다, 합격입니다.Object.values(ingrediants)이펙트를 사용하기 위한 두 번째 인수로 사용합니다.
useEffect(() => {
  setIngredients({});
}, Object.values(ingrediants));

매뉴얼(https://reactjs.org/docs/hooks-effect.html),에 기재되어 있듯이useEffect훅은 모든 렌더링 후에 일부 코드를 실행할 때 사용됩니다.문서에서:

모든 렌더링 후에 useEffect가 실행됩니까?네!

이것을 커스터마이즈 하려면 , 같은 페이지의 후반부에 표시되는 순서에 따릅니다(https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects)).기본적으로는useEffectmethod는 효과가 다시 트리거되어야 하는지 여부를 결정하기 위해 React가 검토하는 두 번째 인수를 받아들입니다.

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

두 번째 인수로 임의의 개체를 전달할 수 있습니다.이 개체가 변경되지 않은 상태로 유지되면 첫 번째 마운트 후에만 효과가 트리거됩니다.개체가 변경되면 효과가 다시 트리거됩니다.

이 방법이 도움이 될지는 모르겠지만 다음과 같이 .length를 추가해 보십시오.

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

내 경우(어레이를 가져오고 있었는데!) 마운트된 데이터를 가져오고 변경 시에만 데이터를 가져오고 루프 상태가 되지 않았습니다.

useEffect 종료 시 빈 배열을 포함하는 경우:

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

그것은 한 번 작동될 것이다.

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

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

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

할 때 .useRef:

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

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

경우, 「 」eslint(react-hooks/exhaustive-deps)ingredients이치노그 re-render)가 합니다. 이 글귀는 이 글귀는, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ㄴ, ᄃ, ᄃ, ᄃ, ᄃ, ᄃ, ᄃ, ᄃ, ᄃ, putting, ᄃ, ᄃ, ingredients.someKey ★★★★★★★★★★★★★★★★★」ingredients.length이치노

해결책은 세터가 참조할 수 있는 오래된 값을 제공하는 것입니다.'이것'을하는 것이 아니라 ''을 사용해야 .ingredients★★★★

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

useEffect(() => {
  setIngredients(oldIngedients => {
    return {
      ...oldIngedients,
      newIngedient: { ... }
    }
  });
}, []);

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

나는 그들이 오래된 데이터를 사용하고 있다는 가능성을 표현하고 있다는 것을 알고 있다고 생각한다.하지 않습니다.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]);

주된 문제는 useEffect가 수신 값과 현재 값을 얕게 비교한다는 것입니다.즉, 개체 참조만 확인하는 '===' 비교를 사용하여 이 두 값을 비교하고 배열과 개체 값은 동일하지만 서로 다른 개체로 취급합니다.라이프 사이클 방법으로서 useEffect에 관한 제 기사를 확인해 보시기 바랍니다.

가장 좋은 방법은 Lodash에서 usePrevious() 및 _.isEqual()사용하여 이전 값과 현재 값을 비교하는 것입니다.isEqualuseRef를 Import합니다.이전 값과 useEffect() 내의 현재 값을 비교합니다.동일한 경우 다른 업데이트를 하지 마십시오.usePrevious(value)는 useRef()를 사용하여 참조를 작성하는 사용자 지정 후크입니다.

아래는 제 코드의 일부입니다.파이어베이스 훅을 사용하여 데이터를 업데이트 할 때 무한 루프 문제에 직면했습니다.

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()
      }
     
  })

를 비교할 , 할 때 이 이 오브젝트를 할 수 없습니다.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]));

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

예를 들어, 재료에 당근이 들어 있다고 가정해 봅시다.우리는 그것을 의존성에 전달할 수 있습니다.그리고 당근이 바뀌었을 경우에만 상태가 갱신됩니다.

그런 다음 특정 포인트에서 당근 수만 업데이트하면 상태가 업데이트되는 시기를 제어하고 무한 루프를 방지할 수 있습니다.

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

이와 같은 기능을 사용할 수 있는 예로는 사용자가 웹 사이트에 로그인하는 경우를 들 수 있습니다.로그인 시 사용자 객체를 구축하여 쿠키와 권한 역할을 추출하고 그에 따라 앱 상태를 업데이트할 수 있습니다.

나의 경우는 무한 루프를 만난 특별한 경우로, 시나리오는 다음과 같습니다.

소품에서 나오는 objX를 소품에서 파괴하는 오브젝트를 가지고 있었습니다.

const { something: { somePropery } } = ObjX

는 '아까운 친구'를 사용했어요.someProperyuseEffect들면 다음과 같습니다.


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

루프를 , 이 모든 시켜서 이 를 해결하려고 .something제대로 작동한 것 같아요

어레이 상태에 사용한 또 다른 작업 솔루션은 다음과 같습니다.

useEffect(() => {
  setIngredients(ingredients.length ? ingredients : null);
}, [ingredients]);

언급URL : https://stackoverflow.com/questions/53070970/infinite-loop-in-useeffect

반응형