본문 바로가기

리액트 공식 문서 읽기

31화: useState

이 글은 단순히 리액트 공식 문서를 읽고 베꼈을 뿐이다!! 학습 목적이라면 공식 문서를 보는 걸 추천한다.

 

부들 줄기에 앉은 참새. Unsplash 에 fred A 님이 올림.

공식 문서

https://react.dev/reference/react/useState

 

useState – React

The library for web and native user interfaces

react.dev

useState(initialState)

  • 컴포넌트에 상태 변수를 추가할 수 있게 해줌
  • initialState
    • 상태 초깃값
    • 첫 렌더링 이후 무시됨
    • 함수를 넣으면 초기화 함수로 작동: argument가 없는 순수함수여야 함. 상태 초깃값을 반환.
  • 반환값: [현재 상태 값, 상태를 바꾸고 리렌더링을 유발하는 set 함수]
  • 훅이기 때문에 컴포넌트 최상단에서만 사용 가능
  • Strict Mode에서는 초기화 함수를 두 번 불러서 순수성을 검사함

상태 설정 함수: setSomething(nextState)

  • 상태 값을 바꾸고 리렌더링을 유발
  • nextState
    • 다음 상태를 입력
    • 함수를 넣을 경우 updater 함수로 작동: pending state를 유일한 argument로 받는 순수함수. 다음 상태값을 반환해야 함.
  • 반환값: 없음(void)
  • 상태 설정 함수는 "다음" 렌더링을 위한 상태 업데이트를 하는 것: 상태 설정 함수를 부르고 코드의 바로 다음 줄에서 상태 값을 읽으면 옛날 값을 보여줌
  • 새로운 상태 값이 현재 상태와 같다면 (Object.is로 비교) 해당 컴포넌트와 그 자식들의 리렌더링을 건너뜀
  • 리액트는 상태 변경을 묶어서 처리함: 하나의 사건(event)에 대응하는 핸들러가 전부 실행된 후에 화면 업데이트 실시
  • 렌더링 '도중에' 상태를 설정하는 건 현재 렌더 중인 컴포넌트 안에서만 허용됨. 리액트는 해당 렌더 결과를 즉시 버리고 새로운 상태값으로 다음 렌더링을 실시함
  • Strict Mode에서는 updater 함수를 두 번 불러서 순수성을 검사함

사용법

컴포넌트에 상태 추가하기

  • 클래식. const [myState, setMyState] = useState(initialState);
  • 상태 설정 함수로 바꾼 값은 "다음" 렌더링 때 상태 값으로 불러올 수 있다는 거 까먹지 말기

이전 상태에 기반하여 상태 바꾸기

  • 상태는 snapshot
<button
  onClick={() => {
    setNumber(number + 1);
    setNumber(number + 1);
    setNumber(number + 1);
  }}
>
  숫자 3만큼 올리기?
</button>

<button
  onClick={() => {
    setNumber(prev => prev + 1);
    setNumber(prev => prev + 1);
    setNumber(prev => prev + 1);
  }}
>
  숫자 3만큼 올리기?
</button>
  • number는 snapshot이라 저 이벤트 핸들러가 실행될 때는 모두 고정된 값
  • updater 함수를 사용할 경우 함수를 실행하지 않고 큐에 쌓음
  • 다음 렌더링 전에 큐를 비우면서 updater 함수를 실행하기 떄문에 prev와 같은 '대기중인 상태(pending state)'는 해당 함수 바로 직전의 상태임

그럼 updater 함수가 무조건 좋겠네?

  • 꼭 그런건 아님
  • 대부분의 경우 updater 함수와 그냥 값 입력 사이에는 차이가 없음
  • 특히 클릭처럼 사용자의 의도적인 행위의 경우 리액트는 항상 다음 클릭 이전까지 상태 변경이 완료되도록 보장함
  • 하나의 이벤트 안에서 여러 업데이트를 수행할 경우 updater 함수가 좋음
  • 꼭 통일해야겠으면 updater 쓰는 쪽으로
  • 만약 새로운 상태가 다른 상태의 옛날 값을 참고하거나 해야 한다면 두 상태를 합치고 reducer 사용을 고려해볼 것

객체나 배열 상태를 바꿀 때

  • 변형(mutate)하지 말고 통째로 교체(replace)하기

초깃값을 여러 번 계산하지 말기

  • 초깃값을 계산해야 한다면 계산 결과를 넣는 게 아니라 계산하는 함수를 넣어서 초기화 함수처럼 동작하게끔 하기

key를 이용해서 상태 초기화하기

  • 컴포넌트의 props로 다른 key 값을 주면 상태와 그 자식들까지 초기화

이전 렌더들의 정보 저장하기

  • 보통은 이벤트 핸들러에서 상태를 바꿈
  • 근데 prop이 바뀌었을 때처럼 '렌더링' 자체에 반응해서 상태를 바꾸고 싶을 수 있음
  • 보통은 아래 세 가지 방법으로 해결 가능함
    • 이 상태가 다른 상태로부터 계산될 수 있다면 이 상태를 없애기. 너무 잦은 계산이 부담되면 useMemo
    • 컴포넌트 트리의 모든 상태를 초기화하고 싶다면 key를 바꾸기
    • 가능하면 관련된 상태들을 이벤트 핸들러에서 다같이 바꾸기
  • 이 세 가지 경우가 아니라면 여태까지 렌더링된 값들을 바탕으로 상태를 바꿀 수 있음: 렌더링 중간에 상태 설정 함수를 불러서.
  • 무한 루프 조심
    • 항상 어떤 조건문 안에서 부르기
    • 조건문 안에서 꼭 조건문에 들어가는 상태값 바꾸기
  • 다른 컴포넌트의 상태 설정 함수를 받아서 쓰면 오류남
  • 당연하지만 참초값 형태의 상태는 변형이 아닌 해야 함
  • 당연하지만 상태 변경은 순수해야 함
  • 일반적으로 이러한 방식은 피하는 게 좋음. 근데 useEffect에서 상태를 변경하는 것보다는 나음
    • 왜? 렌더링 도중에 상태 설정 함수를 쓰면 해당 컴포넌트 함수가 return하자마자 다시 렌더링을 시작함
    • 자식의 리렌더링이나 DOM 반영 전에 렌더링한다는 뜻
    • 하지만 이펙트는 화면 업데이트 후 commit 단계의 맨 마지막에 실행됨 -> 자식들까지 두 번 렌더링함
  • 만약 조건문이 모든 훅들을 부른 후에 있다면 early return을 이용한 빠른 리렌더링 가능

문제 해결하기

상태 바꿨는데 console.log 찍어보면 옛날 상태에요

  • 상태는 shapshot이라고!!!

상태 바꿨는데 화면이 안 변해요

  • Object.is로 비교했을 때 같으면 리렌더링 안함
  • 혹시 상태로 객체나 배열을 쓴다면 변형이 아닌 교체하기

Too many re-renders 에러가 떠요

  • 일반적으로는 조건문 없이 렌더링 도중에 상태를 설정해서 그럼
  • 이벤트 핸들러가 범인인 경우가 많음
  • 자바스크립트 스택을 확인하면 어떤 상태 설정 함수가 범인인지 확인 가능

제 초기화 함수 또는 updater 함수가 두 번씩 실행돼요

  • Strict Mode에서는 순수성을 확인하기 위해 두 번씩 실행한다고!!!

'함수'를 상태로 사용하고 싶은데 함수가 실행돼요

  • 함수를 상태의 초깃값이나 상태 변경 함수에 넣으면 초기화/updater 함수가 됨
  • 함수를 반환하는 함수를 이용해서 해결 가능
// 이러면 someFunction을 실행해버림
const [fn, setFn] = useState(someFunction);
setFn(someOtherFunction);

// 이러면 화살표 함수를 실행하고 그게 someFunction을 반환함
const [fn, setFn] = useState(() => someFunction);
setFn(() => someOtherFunction);