개발자의 공부/React

[React]useReducer

2022. 10. 24. 02:39

useReduer는 컴포넌트에 reducer를 추가할 수 있는 React Hook이다.

상태 업데이트가 많은 컴포넌트들이 많은 이벤트 헨들러에 분산되어 있으면 부담이 커질 수있다.

이런 경우, 컴포넌트 외부의 모든 상태 변화 로직을 reducer라는 단일 기능으로 통합할 수 있다.

쉽게 말자하면, 컴포넌트 내부의 상태를 변경하는 로직이 내부에 작성되어 있어 해당 로직을 다른 컴포넌트에서 재사용할 수 없기에 useReducer를 사용하여 해당 로직을 컴포넌트 외부에서 재사용할 수 있게 된다.

 

reducer는 상태를 처리하는 하나의 방법이며 세 단계를 통해 작성된다.

  1. setting state에서 dispatching actions로 옮긴다.
  2. reducer 함수를 작성한다.
  3. 컴포넌트에서 reducer를 사용한다.

1. setting state에서 dispatching actions로 옮기기.

function handleAddTask(text) {
  setTasks([
    // do Someting
  ]);
}

function handleChangeTask(task) {
  setTasks(
    tasks.map((t) => {
      // do Someting
    })
  );
}

function handleDeleteTask(taskId) {
  setTasks(tasks.filter((t) => t.id !== taskId));
}

상태의 로직들이다. 해당 함수들은 이벤트에 의해 실행되어 상태를 변경 시켰다.

function handleAddTask(text) {
  dispatch({
    type: 'added',
    id: nextId++,
    text: text,
  });
}

function handleChangeTask(task) {
  dispatch({
    type: 'changed',
    task: task,
  });
}

function handleDeleteTask(taskId) {
  dispatch({
    type: 'deleted',
    id: taskId,
  });
}

리듀서로 상태를 관리하는 것은 상태를 직접 설정하는 것과 약간 다르다. 상태를 설정(setting state)하여 React에 "할 일"을 알려주는 것이 아니라 이벤트 헨들러에서 "actions"를 dispatching하여 "사용자가 방금 한 작업"을 지정한다.

한마디로, 이벤트 헨들러를 통해 "설정"하는 대신 dispatching은 "추가/변경/삭제" action을 전달한다.

 

2. reducer 함수 작성하기

  • 현재 상태를 첫 번째 인수로 선언한다.
  • action 객체를 두번째 인수로 선언한다.
  • 리듀서에서 상태를 반환한다.
function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [
        ...tasks,
        {
          id: action.id,
          text: action.text,
          done: false,
        },
      ];
    }
    case 'changed': {
      return tasks.map((t) => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter((t) => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

리듀서 함수는 상태를 인수로 취하기 때문에 컴포넌트 외부에서 선언할 수 있으므로 코드를 개선할 수 있다.

 

3. 컴포넌트에서 reducer 사용하기

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [
        ...tasks,
        {
          id: action.id,
          text: action.text,
          done: false,
        },
      ];
    }
    case 'changed': {
      return tasks.map((t) => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter((t) => t.id !== action.id);
    }
    default: {
      throw Error(`알수없는 액션 타입이다: ${action.type}`);
    }
  }
}

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }
  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task,
    });
  }
  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId,
    });
  }

  return (
    <>
      <h1>Prague itinerary</h1>
      <AddTask onAddTask={handleAddTask} />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

useReducer는 두 가지 인수를 받는다.

  1. Reducer 함수
  2. 초기 상태

위 코드의 동작 원리를 설명하자면 Reducer 함수인 tasksReducer에서 tasks라는 상태를 받아오고, action을 받아온다.

기존의 state와 어떤 action을 원하는 지를 전달 받아서 action의 type이 added인지 changed인지 deleted인지를 판단해서 기존의 객체를 type에 따른 새로운 객체를 반환해주고 있다. 

 

handleAddTask 함수를 살펴보면 dispatch를 호출하고 있다. dispatch가 호출되면 useReducer가 자동으로 Reducer함수인 tasksReducer를 호출하여 기존 상태와 dispatch안에 있는 action객체를 불러오는 것이다. handleAddTask 함수는 type으로 'added'를 가지고 있기에 switch문의 'added' case를 실행하며 기존의 tasks상태에 새로운 객체를 추가하여 반환한다. 그렇게 되면 자동으로 state를 업데이트하여 tasks에 반영된다. 해당 내용이 잘 이해되지 않는다면 👉 공식 문서를 살펴보자.

 

useState와 useReducer 비교하기 

  • 코드의 크기: useReducer는 reducer함수와 dispatch actions를 모두 작성해야하기에 useState에 비해 크기가 커질 수 있지만  많은 이벤트 헨들러가 비슷한 방식으로 상태를 수정하는 경우 코드를 줄이는 데에 도움을 준다.
  • 가독성: useState는 상태 업데이트가 단순할 때 읽기가 쉽지만 복잡해지면 컴포넌트의 크기가 커져 가독성이 떨어질 수 있다. 이런 경우 Reducer를 사용하여 event handler에서 발생한 type과 type에 따른 logic이 명확하게 구분된다.
  • 디버깅: useState의 상태 변화의 잘못된 부분을 알아채기 어렵다. useReducer는 console.log를 Reducer에 추가하여 모든 상태 업데이트와 발생한 이유를 확인할 수 있다. 각 action들이 맞다면, Reducer 로직 자체에 문제가 있다는 것을 알 수 있다. 하지만 useState를 사용하는 것보다 더 많은 코드를 단계별로 실행해야한다.
  • 테스팅: Reduer는 컴포넌트에 의존하지 않는 순수 함수이기에 별도로 내보내고 테스트할 수 있다.
  • 컴포넌트에서 잘못된 상태 업데이트로 인한 버그가 자주 발생하고 코드에 더 많은 구조를 도입하려는 경우 Reducer를 사용하자. 모든 것에 Reducer를 사용할 필요는 없다. 적절한 때에 혼합하여 사용하자.

Reducer 작성 팁

  • Reducer는 순수해야 한다. state updater function과 유사하게 렌더링 중에 실행된다. 요청을 보내거나 시간 초과를 예약하거나 side Effect를 수행해서는 안된다.
  •  데이터가 여러번 변경되더라도 각 action들은 단일 사용자 상호작용(single user interaction)을 설명한다. 예를 들어 사용자가 Reducer에서 관리하는 5개의 필드가 있는 양식에서 "reset"을 누르면 5개의 개별 set_field 작업보다 하나의 reset_form 작업을 전달하는 것이 더 합리적이다. Reducer의 모든 작업을 기록하는 경우 해당 로그는 어떤 상호 작용이나 응답이 어떤 순서로 발생했는지 재구성할 수 있을 만큼 충분히 명확해야 한다. 이것은 디버깅에 도움이 된다.

 

Immer 라이브러리를 사용하여 Reducer 관리하기

immer는 불변 상태를 보다 편리하게 작업할 수 있게 하는 라이브러리이다.

 

설치

npm install immer use-immer
yarn add immer use-immer
저작자표시 비영리 동일조건 (새창열림)

'개발자의 공부 > React' 카테고리의 다른 글

[React]Purity and Side Effect  (0) 2022.11.08
[React]useEffect  (0) 2022.11.08
[React]State(내용 업데이트)  (0) 2022.10.24
[React]Page Routing  (0) 2022.09.30
[React]성능 최적화와 성능 체크(업데이트)  (1) 2022.09.29
'개발자의 공부/React' 카테고리의 다른 글
  • [React]Purity and Side Effect
  • [React]useEffect
  • [React]State(내용 업데이트)
  • [React]Page Routing
JMins
JMins
안녕하세요. 프론트엔드 개발자 김정민입니다. 개발 지식을 공유하고 기록하는 공간입니다.
JMins
개발자 노트
JMins
전체
오늘
어제
  • 분류 전체보기 (85)
    • 개발자의 공부 (73)
      • React (17)
      • 자료구조&알고리즘 (28)
      • JS (17)
      • TS (8)
      • Nodejs (0)
      • Nextjs (1)
      • 기타 (1)
      • Design Pattenrs (0)
      • 테스트 및 최적화 (1)
    • 문제 및 해결 (9)
    • 기본 지식 (3)
    • 챌린지 (0)

블로그 메뉴

  • 홈

공지사항

  • #블로그 스킨 변경
  • 개인적으로 공부를 기록하기 위해 적고 있습니다.

인기 글

최근 댓글

최근 글

hELLO · Designed By 정상우.
JMins
[React]useReducer
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.