useState vs. useReducer (feat. lazy Initialization)

남다은·2024년 4월 10일
2

React

목록 보기
4/8
post-thumbnail

상태관리가 필요한 이유?

그 전에... 상태는 뭐지?

  • 리액트에서 상태란, 컴포넌트 내부에서 변경될 수 있는 동적이고 렌더링에 영향을 주는 값이다.
  • React는 JSX 문법으로 이루어진 코드로, 가상 DOM(Virtual DOM)을 생성하여 실제 DOM에 변경 사항을 반영한다.
  • 이때 가상 DOM은 상태 값 변화에 따라 컴포넌트가 재구성되고 리렌더링이 이뤄진다. 따라서 불필요한 렌더링을 줄이기 위해 적절한 상태관리가 필요한 것이다.

useState

const [state, setState] = useState(initialState)

🧤 initialState : 초기 값

  • 초기 값을 useState 함수에 인자로 넣어주면 state와 setState 두 가지 요소를 배열 형태로 리턴해준다.

🧤 state(첫 번째 원소) : 현재 상태
🧤 setState(두 번째 원소) : Setter 함수

  • [something, setSomething]의 형식으로 구성되어 있다.
  • setter 함수는 파라미터로 전달 받은 값을 최신 상태로 설정해준다.

예시 코드

const Counter = () => {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  const decrement = () => setCount((currentCount) => currentCount - 1);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  )
}

🌱 useState를 언제 사용하지?

  • 상태 값이 불규칙하게 변할 때 사용한다.
  • 단순한 값(number, boolean 등)에 대한 상태관리를 할 때 사용한다.

useReducer

useReducer은 useState와 비슷한 형태를 띠지만 좀 더 복잡한 상태 값시나리오에 따라 관리할 수 있다.

const [state, dispatch] = useReducer(reducer, initialArg, init?)
                                                                                 

반환값

useState와 동일하게 길이가 2인 배열이다.

🧤 state

  • 현재 useReducer가 가지고 있는 값을 의미한다.

🧤 dispatcher

  • state를 업데이트하는 함수.
  • useState의 setState는 단순히 값을 넘겨주지만 dispatcher의 경우 action을 넘겨준다는 점에서 차이가 있다.
    - action은 state를 변경할 수 있는 액션을 의미한다.

인자

useState의 인수와 달리 2개에서 3개의 인수를 필요로 한다.

🧤 reducer

  • useReducer의 기본 action을 정의하는 함수

🧤 inititialState

  • useState의 초깃값을 의미한다.

🧤 init

  • useState의 인수로 함수를 넘겨줄 때처럼 초깃값을 지연 생성하고 싶을 때 사용하는 함수이다.
  • 필수값이 아니며, init에 넘겨주는 함수가 존재하는 경우 useState와 동일하게 게으른 초기화가 일어나게 된다.

예시 코드

const reducer = (state, action) => {
  switch (action) {
    case 'increment':
      return state + 1
    case 'decrement':
      return state - 1
  }
}

function App() {
  const [count, dispatch] = useReducer(reducer, 0)

  return (
    <div>
      Count: {count}
      <button onClick={() => dispatch('increment')}>Increment</button>
      <button onClick={() => dispatch('decrement')}>Decrement</button>
    </div>
  )
}

🌱 useReducer를 언제 사용하는 게 좋을까?

  • 위의 예시 코드를 보면 알 수 있듯이, useReducer은 dispatcher를 통해 정해진 시나리오를 제한적으로 두고 이에 대한 변경을 빠르게 확인할 수 있게끔 한다.
  • 따라서 number나 boolean과 같이 간단한 값에 대한 상태 관리는 useState로 충분하지만 state 하나가 가져야 할 값이 복잡하고, 이를 수정해야 할 경우의 수가 많아지는 경우 useReducer로 관리하는 게 효율적일 수 있다.


Lazy Initialization

useState에 변수 대신 함수를 넘기는 것을 게으른 초기화(lazy Initialization) 라고 한다.

  • 위의 게으른 초기화 함수는 state가 처음 만들어질 때만 사용되며, 만약 이후에 리렌더링이 발생된다면 이 함수의 실행은 무시된다.
  • 추가로, useState(함수())와 같이 호출하는 방식으로 return 값으로 초기값을 받아오는 경우에는 컴포넌트가 리렌더링 될 때마다 실행된다고 한다.
    -> 항상 바쁘게 랜더링이 일어나는 게 아니라 필요할 때 한 번 호출되는 것이기 때문에 '게으른' 초기화가 된 것이다!

🌱 게으른 초기화가 왜 필요하지?

  • 함수 컴포넌트가 렌더링되는 경우, 그 안에 있는 코드들도 다 렌더링이 된다.
  • 렌더링이 될 때마다 함수 내부의 변수나 인자 값들이 모두 새로 만들어진다. 그렇기 때문에 초기값이 많은 비용을 차지하는 경우 매번 렌더링되게 하지 않고 함수 호출을 첫 렌더링 때만 하게 하면서 메모리 낭비를 줄일 수 있다.

    결국 게으른 초기화는 성능 최적화 작업이다!

🌱 언제 게으른 초기화를 할까?

  • 초기값이 간단한 값인 경우 함수가 게으른 초기화를 하게 되면, 초기화를 위해 한 번 호출될 때 함수를 만드는 비용이 존재하며 이 비용은 간단한 값을 넘기거나 변수를 생성하는 비용보다 크게 발생하며, 과한 최적화가 된다.
  • 따라서 게으른 초기화는
    1. localStoragesessionStorage에 대한 접근
    2. map, filter, find와 같은 배열에 대한 접근
    3. 초깃값 계산을 위해 함수 호출이 필요한 경우
    과 같이 무거운 연산이나 실행 비용이 많이 드는 경우 사용하는 것이 좋다.

출처
https://velog.io/@dee0518/React-useReducer
https://yceffort.kr/2020/10/IIFE-on-use-state-of-react
https://jihyun-hamster.tistory.com/128
https://velog.io/@hoyaho/%EC%A7%80%EC%97%B0-%EC%B4%88%EA%B8%B0%ED%99%94

profile
주저리주저리 생각 정리

9개의 댓글

comment-user-thumbnail
2024년 4월 10일

게으른 초기화를 언제 사용하는지, useReducer를 언제 사용하면 좋을지에 대해 정리해주셔서
좀 더 자세하게 개념들을 이해할 수 있었던 거 같아요! 감사합니다 :)

답글 달기
comment-user-thumbnail
2024년 4월 11일

오오 깔끔하게 작성 잘해주셨네요😊 글 잘 봤습니다! useState의 예제 부분이랑 useReducer의 예제부분을 같은 상황, 내용으로 해주셔서 이해가 더 잘 된 것 같네요 ㅎㅎ 게으른 초기화도 우리가 자주쓰는 키워드 (map, localStorage ...)들이랑 엮어서 사용 상황을 제시해주셔서 기억하기도 좋을 것같아요 !! 🫶
넘 좋은 글 감사합니다🤭 수고하셨어요!

답글 달기
comment-user-thumbnail
2024년 4월 11일

평소에 useState만 써왔는데 이번 기회에 useReducer를 사용해보면 좋겠다는 생각이 들었습니다.
또한 게으른 초기화를 통해 최적화를 할 수 있다는 사실도 처음 알게 되었네요ㅎ
감사합니다!!

답글 달기
comment-user-thumbnail
2024년 4월 11일

useState와 useReducer의 각각의 개념을 친절하게 설명해 주어서 이해하기 너무 편했습니다.
또한, 어느 상황에서 useState 또는 useReducer를 사용하면 좋은지 예시까지 들어주어서 좋았습니다.

답글 달기
comment-user-thumbnail
2024년 4월 11일

깔끔하게 잘 정리된 아티클 잘 보고갑니다!
상태관리의 대표 hook인 useState와 useReducer에 대한 설명 전에 상태 관리, 상태에 대한 개념이 있어서 왜 상태관리 hook 함수를 공부해야 하는지에 대해 알고 보게 되어서 아티클이 더 와닿은것 같아요~ 또 useState와 useReducer의 차이점을 짚어주면서 설명해줘서 둘의 차이점도 확실하게 알고 갑니다! 특히 게으른 초기화 부분에서 초반에 설명한 상태관리의 필요성인 불필요한 랜더링의 최소화에 대한 관점을 강조해서 써주셔서 글 전체에 대한 완성도가 더 높아진거 같아요! 좋은 아티클 써주셔서 감사합니다 수고많으셨습니다~~!!

답글 달기
comment-user-thumbnail
2024년 4월 11일

useState 초기값 설정하는 부분에 함수를 넣을 수 있다는 걸 새롭게 알게되었습니다.
실행 비용이 많이 들 때 게으른 초기화를 사용한다는 개념은 알고있었는데 실행 비용이 많이 들 때가 언젠지에 대한 개념이 모호했는데 비용이 많이 드는 경우도 예시로 잘 소개해주셔서 이해하기 수월했습니다.
useState, useReducer 전에 상태관리가 필요한 이유를 먼저 설명해주신 부분도 해당 hook들을 이해하는 데에 큰 도움이 됐습니다.
유용한 아티클 감사합니당 :) 좋은 정보 많이 얻고갑니다 !!

답글 달기
comment-user-thumbnail
2024년 4월 11일

다은님 아티클 읽고 리액트의 상태관리에 대해 더 자세히 알 수 있었습니다!
useState는 단순한 값의 상태 관리에 적합하고, useReducer는 좀 더 복잡한 상태 로직이나 여러 하위 값들을 포함한 상태를 관리할 때 유용하다는 것을 알 수 있었으며, 언제 useState 와 useReducer 을 사용하는지 예시를 들어주어서 더 쉽게 이해할 수 있었어요!
특히 게으른 초기화에 대한 내용을 통해 리액트의 상태관리가 단순히 상태를 업데이트 하는 것 뿐만 아니라, 어떻게 더 효율적으로 상태를 관리하고 최적화할 수 있는지 더 생각해볼 수 있었습니다!
아티클 너무 잘 읽었습니다! 수고하셨어요!!

답글 달기
comment-user-thumbnail
2024년 4월 11일

useReducer에 대해 잘 몰랐는데, 예제와 비교를 통해 설명해주신 덕분에 자세히 알아갑니다 😊

그 밖에도 상태관리, useState, 게으른 초기화 등의 개념에 대해 더욱 자세히 집고 넘어갈 수 있는 글이네요 ㅎㅎ 좋은 글 감사합니다 !

답글 달기
comment-user-thumbnail
2024년 4월 11일

상태 관리가 필요한 이유부터 자세히 설명해 주셔서 이해하기 너무 좋았습니다!!
useState는 사용해 보았으나 useReducer는 사용해보지 못했는데, 예제를 사용해서 쉽게 설명해 주셔서 차이점과 사용법을 쉽게 이해할 수 있었습니다! 특히 게으른 초기화를 사용하는 경우 예시를 들어주셔서 더 쉽게 이해가 된 것 같아요! 깔끔한 설명 감사합니다~~

답글 달기