본문 바로가기
스터디/2024 - 리액트 스터디

setState Hooks 평문에 써보기

by 1005ptr 2024. 8. 3.
반응형
결론 : setState도 동등 비교 같은거 하는줄 알았는데 안하는줄 알았는데 한다.

 

setState를 Hooks 평문에 쓰면 안된다

왜 안될까?

아래 코드를 실행하면 무한 루프를 돌다가 종료된다.

왜 무한루프를 돌까?

setter 함수가 리렌더를 발생시킨다.

setter 함수에는 동일값인지 비교하는 부분이 없다. setter가 호출되면 무조건 리렌더가 발생한다.

중간중간에 초기값이 들어갔다 나오는 이유는 알수 없음.

import React,{useState, useEffect} from 'react';

export function App(props) {
  const [state, setState] = useState(10);

  useEffect(()=>{
    setState(1);
  console.log("init: " + state);
  },[]);
  console.log(state);
  setState(2);
  return (
    <div>{state}</div>
  );
}

 

실행 결과

더보기

10
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
10
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
    at renderWithHooks (<anonymous>:12170:25)
    at mountIndeterminateComponent (<anonymous>:15575:23)
    at beginWork (<anonymous>:16592:24)
    at HTMLUnknownElement.callCallback (<anonymous>:3336:24)
    at Object.invokeGuardedCallbackDev (<anonymous>:3363:26)
    at invokeGuardedCallback (<anonymous>:3397:41)
    at beginWork$1 (<anonymous>:20013:17)
    at performUnitOfWork (<anonymous>:19458:22)
    at workLoopSync (<anonymous>:19392:15)
10
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
10
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
    at renderWithHooks (<anonymous>:12170:25)
    at mountIndeterminateComponent (<anonymous>:15575:23)
    at beginWork (<anonymous>:16592:24)
    at HTMLUnknownElement.callCallback (<anonymous>:3336:24)
    at Object.invokeGuardedCallbackDev (<anonymous>:3363:26)
    at invokeGuardedCallback (<anonymous>:3397:41)
    at beginWork$1 (<anonymous>:20013:17)
    at performUnitOfWork (<anonymous>:19458:22)
    at workLoopSync (<anonymous>:19392:15)
The above error occurred in the <App> component:

    at App (<anonymous>:37:55)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
    at renderWithHooks (<anonymous>:12170:25)
    at mountIndeterminateComponent (<anonymous>:15575:23)
    at beginWork (<anonymous>:16592:24)
    at beginWork$1 (<anonymous>:20001:24)
    at performUnitOfWork (<anonymous>:19458:22)
    at workLoopSync (<anonymous>:19392:15)
    at renderRootSync (<anonymous>:19370:17)
    at recoverFromConcurrentError (<anonymous>:18968:30)
    at performConcurrentWorkOnRoot (<anonymous>:18915:32)


아래 코드 실행 결과를 확인해보자

밑에서 알수 있는 사실

  • 여러번 호출되면 마지막 값이 세팅된다.
  • Hooks는 등록된 순서대로 실행된다.
import React,{useState, useEffect} from 'react';

export function App(props) {
  const [state, setState] = useState(10);
  console.log('rendered');

  useEffect(()=>{
    setState(3);
    console.log("dep1: " + state);
  }, [state]);

  useEffect(()=>{
    setState(1);
    console.log("init: " + state);
  },[]);

  useEffect(()=>{
    setState(2);
    console.log("dep2: " + state);
  }, [state]);

  return (
    <div>{state}</div>
  );
}

// Log to console
console.log('Hello console')

실행 결과

더보기

Hello console
rendered
dep1: 10
init: 10
dep2: 10
rendered
dep1: 2
dep2: 2
rendered


useEffect의 의존성 배열은 실행할지 말지 판단할 때 동등비교를 수행한다.

밑에서 알수 있는 사실

  • 마지막 설정된 값이 동일한 값이어서 초기화 이후에 추가 리렌더가 발생하지 않았음
import React,{useState, useEffect} from 'react';

export function App(props) {
  const [state, setState] = useState(10);
  console.log('rendered');

  useEffect(()=>{
    setState(1);
    console.log("init: " + state);
  },[]);

  useEffect(()=>{
    setState(3);
    console.log("dep1: " + state);
  }, [state]);

  useEffect(()=>{
    setState(10);
    console.log("dep2: " + state);
  }, [state]);

  return (
    <div>{state}</div>
  );
}

// Log to console
console.log('Hello console')

실행 결과

더보기

Hello console
rendered
init: 10
dep1: 10
dep2: 10
rendered


추가

  • 인줄 알았는데 초기값 10에서 10으로 setState 하니까 리렌더가 안됐다.
  • 아하...
  • 그냥 밖에서 했을때 무한루프 도는건 그냥 리액트 규격 외라서 이상하게 동작하는 거였나보다.
import React,{useState, useEffect} from 'react';

export function App(props) {
  const [state, setState] = useState(10);

  useEffect(()=>{
    setState(10);
    console.log("init: " + state);
  },[]);
  console.log("rendered: " + state);
  return (
    <div>{state}</div>
  );
}

실행결과

더보기

rendered: 10
init: 10


그럼 동일값 비교는 어디서 되는가?

  • shouldComponentUpdate
  • React.memo
  • 의존성 배열 - userEffect, useMemo, useCallback

 

 

반응형

댓글