React Hooks :: useReducer에 대해 알아보기

Hayoung·2021년 5월 1일
14

React

목록 보기
2/2

오늘은 useReducer에 대해 학습해보았기 때문에, 그 기록을 남겨봅니다.

useReducer란?🤔

React에는 기본적으로 제공하는 내장 훅(Built-in Hooks)이 있는데,
useReducer는 React 내장 훅 중의 하나이다.
(Hook이 무엇인지에 대해 알고 싶다면 이 부분을 참고!)

useReducer의 역할

위와 같이 React 공식 문서에서 언급된 것처럼,
useReducerState(상태)를 관리하고 업데이트하는 Hook인 useState를 대체할 수 있는 Hook이다.
다시 말해, useReduceruseState처럼 State를 관리하고 업데이트 할 수 있는 Hook이다.

여기서 잠깐✋
중요한 포인트.

useReducer의 묘미는,
⭐️한 컴포넌트 내에서 State를 업데이트하는 로직 부분을 그 컴포넌트로부터 분리시키는 것을 가능⭐️하게 해준다는 것이다.
그렇게 useReducerState 업데이트 로직을 분리하여
컴포넌트의 외부에 작성하는 것을 가능하게 함으로써, 코드의 최적화를 이루게 해준다.

🙄 ????

이게 무슨 뜻이냐?
React 튜토리얼에서 자주 등장하는 Counter 컴포넌트를
각각 useState, useReducer를 이용하여 만들어보았다.

아래의 두 코드를 비교해보자.
(코드 내용은 일단 스킵하고,
State 업데이트 로직이 어디에 위치했는지 큰 구조만 살펴 보자 🔍)

  • useState를 사용한 경우 : 컴포넌트 내부State 업데이트 로직이 존재

  • useReducer를 사용한 경우 : 컴포넌트 외부State 업데이트 로직이 존재

이쯤에서 다시 한 번 위의 설명을 읽어보자.

useReducer의 묘미는, ⭐️한 컴포넌트 내에서 State를 업데이트하는 로직 부분을
그 컴포넌트로부터 분리시키는 것을 가능
⭐️하게 해준다는 것이다.

'분리시키는 것을 가능하게 해준다'는 것은,
State 업데이트 로직을 또다른 파일에 작성해서 (분리), 분리된 파일을 불러와서 사용하는 것도 가능하다는 뜻도 된다.

이렇게 useReducer를 사용하면 컴포넌트의 최적화를 이룰 수 있다.

useReducer 🆚 useState

useReducer, useState 둘다 State를 변경하고 관리할 때 사용할 수 있다는 것을 알았다.
그럼 언제 useReducer를 사용하며, 언제 useState를 사용해야 할까?
정답은 없다고 하나, (본인이 조사해본 바) 여러 블로그에서 아래와 같이 소개하고 있는 것 같다.

  • useState

    • 관리해야 할 State가 1개일 경우
    • State가 단순한 숫자, 문자열 또는 Boolean 값일 경우
  • useReducer

    • 관리해야 할 State가 1개 이상, 복수일 경우
    • 혹은 현재는 단일 State 값만 관리하지만, 추후 유동적일 가능성이 있는 경우
    • 스케일이 큰 프로젝트의 경우
    • State의 구조가 복잡해질 것으로 보이는 경우

useReducer의 기본 코드 구조

자, 위의 챕터를 읽었다면 useReducer를 언제 쓰는지, 왜 쓰는지에 대해서
대~충 감이 왔을거라고 생각이 든다.

이제 본격적으로 useReducer의 사용법과 구조에 대해 살펴보자.

전체 샘플 코드 🔍

React 튜토리얼에서 흔히 볼 수 있는 Counter 앱이다.
실제 Counter 앱의 움직임과 함께 코드를 보고 싶다면 여기로.

import React, { useReducer } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "decrement":
      // action의 type이 "decrement"일 때, 현재 state 객체의 count에서 1을 뺀 값을 반환함
      return { count: state.count - 1 };
    case "increment":
      // action의 type이 "increment"일 때, 현재 state 객체의 count에서 1을 더한 값을 반환함
      return { count: state.count + 1 };
    default:
      // 정의되지 않은 action type이 넘어왔을 때는 에러를 발생시킴
      throw new Error("Unsupported action type:", action.type);
  }
}

function Counter() {
  const [number, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      {/* 현재 카운트 값은 state인 number 객체의 count로부터 읽어옴 */}
      <h1>Count: {number.count}</h1>
      {/* 카운트 값의 변경을 위해 각 버튼이 클릭되면 dispatch 함수가 발동되면서 reducer 함수가 실행됨.
          dispatch 함수의 인자로, action 객체가 설정되었는데,
          action 객체의 type에는 어떤 버튼을 클릭하였는지에 따라
          "decrement" 또는 "increment"가 들어감
      */}
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
}

export default Counter;

useReducer를 사용하기 위한 구성 요소

크게 4가지가 있다.

1️⃣ useReducer 함수

useReducer 함수는 첫번째 인자인 4️⃣ reducer 함수반환(return)하는 값으로 state를 갱신하는 역할을 한다.

기본적으로 useReducer는 다음과 같은 형태로 사용한다.

const [state, dispatch] = useReducer(reducer, initialState, init);
  • state: 컴포넌트에서 사용할 State(상태).
  • dispatch: 4️⃣ reducer 함수를 실행시키며, 컴포넌트 내에서 state의 업데이트를 일으키기 위해서 사용하는 함수. 자세한 것은 아래 3️⃣ dispatch 함수 항목을 참고.
  • reducer: 컴포넌트 외부에서 state를 업데이트하는 로직을 담당하는 함수. 현재의 stateaction 객체를 인자로 받아서, 기존의 state대체(replace)할 새로운 State반환(return)하는 함수. 자세한 것은 아래 4️⃣ reducer 함수 항목을 참고.
  • initialState: 초기 State
  • init: 초기 함수

전체 샘플 코드에서는 useReducer 함수를 다음과 같이 작성해주었다.

const [number, dispatch] = useReducer(reducer, 0);

2️⃣ action

action업데이트를 위한 정보를 가지고 있는 것이며, dispatch의 인자가 되며,
4️⃣ reducer 함수의 두번째 인자인 action에 할당된다.
action은 따로 정해진 형태는 없으나
아래의 코드와 같이 주로 type라는 값을 지닌 객체 형태로 사용된다고 한다.
우리의 샘플 코드를 예로 들면, { type: "decrement" } ← 이 부분이 action이다.

dispatch({ type: "decrement" })

3️⃣ dispatch 함수

dispatch 함수4️⃣ reducer 함수를 실행시킨다.
dispatch 함수의 인자로써 업데이트를 위한 정보를 가진 2️⃣ action를 이용하여,
컴포넌트 내에서 state의 업데이트를 일으키기 위해서 사용
된다.
dispatch 함수의 인자인 2️⃣ action4️⃣ reducer 함수의 두번째 인자인 action에 할당된다.

function Counter() {
  const [number, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      {/* 현재 카운트 값은 state인 number 객체의 count로부터 읽어옴 */}
      <h1>Count: {number.count}</h1>
      {/* 카운트 값의 변경을 위해 각 버튼이 클릭되면 dispatch 함수가 발동되면서 reducer 함수가 실행됨.
          dispatch 함수의 인자로, action 객체가 설정되었는데,
          action 객체의 type에는 어떤 버튼을 클릭하였는지에 따라
          "decrement" 또는 "increment"가 들어감
      */}
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
}

4️⃣ reducer 함수

reducer 함수3️⃣ dispatch 함수에 의해 실행되며, 컴포넌트 외부에서 state를 업데이트하는 로직을 담당한다.

1️⃣ useReducer 함수의 첫번째 파라미터로 입력된 reducer 함수는,
현재의 state2️⃣ action을 인자로 받게 되는데,
2️⃣ action의 값에 근거하여 기존의 state대체(replace)할 새로운 state반환(return)
한다.

아래 reducer 함수의 코드에서는, 간편하게 switch문을 이용하여
2️⃣ action의 값이 무엇인지에 따라 새로운 state반환(return)하고 있다.

(switch문을 사용하든, if문을 사용하든 방법은 자유.
이번에 샘플 코드에서 switch문을 사용한 이유는
if문을 사용하게 되면 2️⃣ action의 값에 맞춘 조건으로써 else if로 계속 처리를 만들어줘야하고,
연산자도 써줘야해서 번거롭기 때문.)

function reducer(state, action) {
  switch (action.type) {
    case "decrement":
      // action의 type이 "decrement"일 때, 현재 state에서 1을 뺀 값을 반환함
      return state - 1;
    case "increment":
      // action의 type이 "increment"일 때, 현재 state에서 1을 더한 값을 반환함
      return state + 1;
    default:
      // 정의되지 않은 action type이 넘어왔을 때는 에러를 발생시킴
      throw new Error("Unsupported action type:", action.type);
  }
}

reducer 함수에서 짚고 넘어가야할 점은,
기존의 state를 새로운 state대체(replace)한다는 것이다.
기존의 state변경(modify)하거나, 추가(add)하거나, 덮어쓰지(overwrite) 않는다는 것.


Demo 🤓


마치며

난 아직 Redux의 학습을 시작하지 않은 상태라 Redux에 대해 잘 알지는 못하지만,
Redux 또한 State management 기능을 수행하기 때문에
useReducer를 이해했다면 Redux의 기초를 이해하는 것에 도움이 된다고 한다.
(일석이조 🐶 이득❣️)

사실 이번 예시도 useReducer의 진가를 보여주기에는 부족한 예시였다고 생각하지만
간단한 예시를 통해 조금이라도 useReducer에 다가갈 수 있었기를 바란다.

조금 설명하기 어렵고 난해한(?) 부분이 없지 않아 있었던 것 같다.
그래서 꽤 장황하게 설명한 것 같지만...😭
이 글이 useReducer를 이해하고 싶은 누군가에게 유용한 자료가 되길 바라며...

잘못된 정보가 있으면 마구마구 지적해주시면 감사하겠습니다!

profile
Frontend Developer. 블로그 이사했어요 🚚 → https://iamhayoung.dev

3개의 댓글

comment-user-thumbnail
2022년 8월 16일

많은 도움이 되었습니다 감사합니다

답글 달기
comment-user-thumbnail
2022년 11월 2일

감사합니다 :)

답글 달기
comment-user-thumbnail
2022년 12월 21일

와 정말 감사합니다!!! 블로그 본것중에 역대급으로 잘 이해가 잘가서 댓글 남깁니다!! ㅠㅠ
진짜..글 하나하나 너무 정성스럽네용 ㅠㅠ useContext부터 타고 읽고왔습니다!

다만..아직까지
기존의 state를 새로운 state로 대체(replace) 한다는 개념이 아직까지는 어려운것같아용 ㅠㅠ
대체한다는 개념이 변경이나 덮어쓰지 않는다는 거랑 뭐가 다른건지 잘 와닿지가..않아서
개념이 아직 어려운거같아유 ㅠㅠㅠ

답글 달기