useEffect-상태를 업데이트 할 때 무한 루프 방지


9

사용자가 할 일 목록을 정렬 할 수 있기를 바랍니다. 드롭 다운에서 사용자 선택 항목은이 설정합니다 때 sortKey의 새 버전을 만들 것이다 setSortedTodos, 및 회전을 트리거에서 useEffect와 전화를 setSortedTodos.

아래 예제는 원하는 방식대로 작동하지만 eslint는 의존성 배열 에 추가하라는 메시지 todos를 표시하며 useEffect, 그렇게하면 무한 루프가 발생합니다 (예상 한대로).

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

라이브 예 :

나는 eslint를 행복하게하는 더 좋은 방법이 있어야한다고 생각합니다.


1
참고 사항 : sort콜백은 다음과 같습니다. return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());환경에 합리적인 로캘 정보가있는 경우 로캘 비교를 수행 할 수 있다는 이점이 있습니다. 당신이 좋아하는 경우에, 당신도, 그것을 destructuring 던질 수 : pastebin.com/7X4M1XTH
TJ 크라우

어떤 오류가 발생 eslint합니까?
Luze

스택 스 니펫 ( 도구 모음 단추)을 사용하여 실행 가능한 최소한의 재현 가능한 예제 를 제공하도록 질문을 업데이트 할 수 [<>]있습니까? 스택 스 니펫은 JSX를 포함한 React를 지원합니다. 다음 중 하나를 수행하는 방법이 있습니다. 그렇게하면 사람들이 제안한 솔루션에 무한 루프 문제가 없는지 확인할 수 있습니다.
TJ Crowder

그것은 정말 흥미로운 접근법이고 정말 흥미로운 문제입니다. 당신이 말했듯이, ESLint가 todos에 의존성 배열에 추가해야한다고 생각하는 이유를 이해할 useEffect수 있으며 왜 그렇게하지 않아야하는지 알 수 있습니다. :-)
TJ Crowder

나는 당신을 위해 라이브 예제를 추가했습니다. 이 답변을 정말로보고 싶습니다.
TJ Crowder

답변:


8

나는 이것이 이런 식으로 진행하는 것이 이상적이지 않다는 것을 의미한다고 주장한다. 이 기능은에 의존합니다 todos. 경우 setTodos다른 곳이라고, 콜백 함수는 다시 계산해야합니다 그렇지 않으면 오래된 데이터에서 작동합니다.

어쨌든 정렬 된 배열을 상태에 저장합니까? useMemo키 또는 배열이 변경 될 때 값을 정렬하는 데 사용할 수 있습니다 .

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

그런 다음 sortedTodos어디에서나 참조하십시오 .

라이브 예 :

정렬 된 값을 상태에 저장할 필요는 없습니다. 정렬 된 배열은 항상 "기본"배열과 정렬 키에서 파생 / 계산할 수 있기 때문입니다. 코드가 덜 복잡하기 때문에 코드를 이해하기 쉽게 만듭니다.


오 잘 사용 useMemo합니다. 부수적 인 질문 인 이유 .localCompare는 무엇입니까?
Tikkes

2
실제로 더 나을 것입니다. 방금 OP 코드를 복사했지만 그 문제에 관심을 기울이지 않았습니다 (실제로 문제와 관련이 없음).
Felix Kling

정말 간단하고 이해하기 쉬운 솔루션.
TJ Crowder

아 네, 메모를 사용하십시오! :)
DanV

3

무한 루프의 이유는 할 일이 이전 참조와 일치하지 않아 효과가 다시 실행되기 때문입니다.

어쨌든 클릭 액션에 효과를 사용하는 이유는 무엇입니까? 다음과 같은 기능으로 실행할 수 있습니다.

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

드롭 다운에서을 수행하십시오 onChange.

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

그건 그렇고 의존성에 주목 해 ESLint가 맞습니다! 위에서 설명한 경우 할 일 목록은 종속 항목이므로 목록에 있어야합니다. 항목 선택에 대한 접근 방식이 잘못되었으므로 문제가 발생합니다.


2
"sort는 배열의 새 인스턴스를 반환합니다" 내장 정렬 방법이 아닙니다. 배열을 제자리에 정렬합니다. data.slice(0)사본을 만듭니다.
Felix Kling

setState기존 객체를 편집하지 않기 때문에 내부적으로 복제하기 때문에 수행 할 때 사실이 아닙니다 . 내 대답에 잘못된 표현이 사실입니다. 이것을 편집하겠습니다.
Tikkes

setState데이터를 복제하지 않습니다. 왜 그렇게 생각하십니까?
Felix Kling

1
@Tikkes-아니요, setState아무것도 복제하지 않습니다. Felix와 OP가 올바른 경우 정렬하기 전에 배열을 복사해야합니다.
TJ Crowder

예, 미안합니다 내부에서 더 읽을 거리가 필요한 것 같습니다. 당신은 실제로 기존을 복사하고 변경하지 않아야합니다 state.
Tikkes

0

여기서해야 할 일은 다음과 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]);

작업 코드 샌드 박스

원래 상태를 변경하지 않기 위해 상태를 복사하는 경우에도 상태를 비동기로 설정하여 최신 값을 얻을 수는 없습니다. 또한 대부분의 메서드는 얕은 복사본을 반환하므로 어쨌든 원래 상태가 변경 될 수 있습니다.

기능 setState을 사용 하면 최신 상태 값을 얻고 원래 상태 값을 변경하지 않습니다.


"원래 상태 값을 변경하지 마십시오." React가 콜백에 사본 을 전달한다고 생각하지 않습니다 . .sort배열을 제자리에서 변경하므로 여전히 직접 복사해야합니다.
Felix Kling

흠, 좋은 지적이다. 실제로 그것이 상태의 사본을 통과한다는 확인을 찾을 수 없다.
Clarity

여기 발견 : reactjs.org/docs/...를 . '우리는 setState의 기능 업데이트 형식을 사용할 수 있습니다. 그것은 우리가 상태를 변경해야하는 방법을 지정할 수 있습니다 현재 상태를 참조하지 않고 '
선명도

나는 그것을 사본을 얻는 것으로 읽지 않습니다. 단지 현재 상태를 "외부"종속성으로 제공 할 필요가 없음을 의미합니다.
Felix Kling
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.