React 공식문서 이해하기 (20)

Syoee·2023년 11월 28일
0

React

목록 보기
20/30
post-thumbnail

Chapter 3. Managing State

#5 State로직을 Reducer로 추출하기

학습 목차

1. reducer로 state 로직 통합하기
2. useStateuseReducer 비교하기
3. reducer 잘 작성하기
4. Immer를 사용하여 간결한 reducer 작성하기


1. reducer로 state 로직 통합하기

  • 컴포넌트가 복잡해지면 컴포넌트의 state가 업데이트되는 다양한 경우를 한눈에 파악하기 어려워질 수 있다.
  • 복잡성을 줄이고 모든 로직을 접근하기 쉽게 한 곳에 모으려면, state 로직을 컴포넌트 외부의 reducer라고 하는 단일 함수로 옮길 수 있다.
  • Reducer는 state를 관리하는 다른 방법이다.
    useState에서 useReducer로 마이그레이션하는 방법은 세 단계로 진행된다.
    1. state를 설정하는 것에서 action들을 전달하는 것으로 변경하기
    2. reducer 함수 작성하기
    3. 컴포넌트에서 reducer 사용하기

1-1. state 설정을 action들의 전달로 바꾸기

  • reducer를 사용한 state 관리는 state를 직접 설정하는 것과 약간 다르다.
    state를 설정하여 React에게 “무엇을 할 지”를 지시하는 대신, 이벤트 핸들러에서 “action”을 전달하여 “사용자가 방금 한 일”을 지정한다.
    state 업데이트 로직은 다른 곳에 있다.
  • JavaScript 객체에는 일반적으로 무슨 일이 일어났는지 에 대한 최소한의 정보를 포함해야 한다.
    dispatch 함수 자체는 이후 단계에서 추가할 것이다.

KeyNote

  • action 객체는 어떤 형태든 될 수 있다.
  • 그렇지만 무슨 일이 일어나는지 설명하는 문자열 타입의 type을 지정하고 추가적인 정보는 다른 필드를 통해 전달하도록 작성하는게 일반적이다.

1-2. reducer 함수 작성하기

  • reducer 함수에 state 로직을 둘 수 있다.
  • 이 함수는 두 개의 매개변수를 가지는데, 하나는 현재 state이고 하나는 action 객체이다.
function yourReducer(state, action) {
  // return next state for React to set
}
  • React는 reducer로부터 반환된 것을 state로 설정할 것이다.
  • state를 설정하는 로직을 이벤트 핸들러에서 reducer 함수로 옮기기 위해서 다음과 같이 진행해 보자.
    현재의 state(tasks)를 첫 번째 매개변수로 선언하라.
    action 객체를 두 번째 매개변수로 선언하라.
    다음 state를 reducer 함수에서 반환하라. (React가 state로 설정할 것입니다.)
function tasksReducer(tasks, action) {
  if (action.type === 'added') {
    return [
      ...tasks,
      {
        id: action.id,
        text: action.text,
        done: false,
      },
    ];
  } else if (action.type === 'changed') {
    return tasks.map((t) => {
      if (t.id === action.task.id) {
        return action.task;
      } else {
        return t;
      }
    });
  } else if (action.type === 'deleted') {
    return tasks.filter((t) => t.id !== action.id);
  } else {
    throw Error('Unknown action: ' + action.type);
  }
}
  • reducer 함수는 state(tasks)를 매개변수로 갖기 때문에, 컴포넌트 밖에서 reducer 함수를 선언할 수 있다.
    이렇게 하면 들여쓰기 단계도 줄이고 코드를 읽기 쉽게 만들 수 있다.

KeyNote

  • 위에 있던 코드는 if/else 구문을 사용한다.
    그러나 reducer 안에서는 switch 구문을 사용하는 게 일반적이다.
    결과는 똑같지만 switch 구문이 한눈에 봐도 읽기 더 편하다.
  • 하지만 아직 switch 구문에 익숙하지 않다면, if/else를 사용하는 것도 전혀 문제되지 않는다.

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

  • 컴포넌트에 tasksReducer를 연결해야한다.
    React에서 useReducer Hook을 import하라

import { useReducer } from 'react';

  • 그런 다음, useState 대신

const [tasks, setTasks] = useState(initialTasks);

  • useReducer로 바꿔주자.

const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

  • useReducer Hook은 useState와 비슷하다.
    초기 state 값을 전달해야 하며, 그 결과로 state 값과 state 설정자 함수(useReducer의 경우 dispatch 함수)를 반환한다.
    하지만 조금 다른 점이 있다.

  • useReducer Hook은 두 개의 인자를 받는다.

    1. reducer 함수
    2. 초기 state
  • 그리고 아래 내용을 반환한다.

    1. state값
    2. dispatch 함수 (사용자의 action을 reducer에 “전달”해주는 함수)
  • 이렇게 관심사를 분리하면 컴포넌트 로직을 더 쉽게 읽을 수 있다.

  • 이제 이벤트 핸들러는 action을 전달하여 무슨 일이 일어났는지 만 지정하고, reducer 함수는 action에 대한 응답으로 state가 어떻게 변경되는지 를 결정합니다.

2. useStateuseReducer 비교하기

  • Reducer도 좋은 점만 있는 것은 아닙니다! 다음은 useState 와 useReducer 를 비교할 수 있는 몇 가지 방법이다.

코드 크기

  • 일반적으로 useState를 사용하면 미리 작성해야 하는 코드가 줄어든다.
  • useReducer를 사용하면 reducer 함수 와 action을 전달하는 부분 모두 작성해야 한다.
    하지만 많은 이벤트 핸들러가 비슷한 방식으로 state를 업데이트하는 경우 useReducer를 사용하면 코드를 줄이는 데 도움이 될 수 있다.

가독성

  • useState로 간단한 state를 업데이트 하는 경우 가독성이 좋다.
  • 그렇지만 state의 구조가 더욱 복잡해지면, 컴포넌트의 코드의 양이 부풀어 오르고 한눈에 읽기 어려워질 수 있다.
  • 이 경우 useReducer를 사용하면 업데이트 로직이 어떻게 동작 하는지와 이벤트 핸들러를 통해 무엇이 일어났는지 를 깔끔하게 분리할 수 있다.

디버깅

  • useState에 버그가 있는 경우, state가 어디서 잘못 설정되었는지, 그리고 그런지 알기 어려울 수 있다.
  • useReducer를 사용하면, reducer에 콘솔 로그를 추가하여 모든 state 업데이트와 (어떤 action으로 인해) 버그가 발생했는지 확인할 수 있다.
  • 각 action이 정확하다면, 버그가 reducer 로직 자체에 있다는 것을 알 수 있다.
    하지만 useState를 사용할 때보다 더 많은 코드를 살펴봐야한다.

테스팅

  • reducer는 컴포넌트에 의존하지 않는 순수한 함수이다.
    즉, 별도로 분리해서 내보내거나 테스트할 수 있다.
  • 일반적으로 보다 현실적인 환경에서 컴포넌트를 테스트하는 것이 가장 좋지만, 복잡한 state 업데이트 로직의 경우 reducer가 특정 초기 state와 action에 대해 특정 state를 반환한다고 단언하는 것이 유용할 수 있다.

개인 취향

  • 어떤 사람은 reducer를 좋아하고 어떤 사람은 싫어한다.
    취향의 차이이므로 괜찮다.
    useStateuseReducer는 언제든지 앞뒤로 변환할 수 있으며, 서로 동등하다.
  • 일부 컴포넌트에서 잘못된 state 업데이트로 인해 버그가 자주 발생하고 코드에 더 많은 구조를 도입하려는 경우 reducer를 사용하는 것이 좋다.
    모든 컴포넌트에 reducer를 사용할 필요는 없으니 자유롭게 섞어서 사용하자.
    심지어 같은 컴포넌트에서 useStateuseReducer를 함께 사용할 수도 있다.

3. reducer 잘 작성하기

  • reducer는 반드시 순수해야 한다.
    state 설정 함수와 비슷하게, reducer는 렌더링 중에 실행된다.
    action은 다음 렌더링까지 대기한다.
    즉, reducer는 반드시 순수해야한다.
    즉, 입력 값이 같다면 결과 값도 항상 같아야 한다.
  • 요청을 보내거나 timeout을 스케쥴링하거나 사이드 이펙트(컴포넌트 외부에 영향을 미치는 작업)을 수행해서는 안된다.
  • reducer는 객체 및 배열을 변이 없이 업데이트해야한다.
  • 각 action은 여러 데이터가 변경되더라도, 하나의 사용자 상호작용을 설명해야 한다.
  • 모든 action을 reducer에 기록하면 어떤 상호작용이나 응답이 어떤 순서로 일어났는지 재구성할 수 있을 만큼 로그가 명확해야한다.
    이는 디버깅에 도움이 된다.

4. Immer를 사용하여 간결한 reducer 작성하기

  • 일반 state의 객체와 배열을 변경할 때와 마찬가지로 Immer 라이브러리를 사용해 reducer를 더 간결하게 만들 수 있다.
  • reducer는 순수해야 하므로 state를 변이하지 않아야한다.

요약

  • useSate에서 useReducer로 변환하기 위해서는
    1. 이벤트 핸들러에서 action을 전달힌다.
    2. 주어진 state와 action에 대해 다음 state를 반환하는 reducer 함수를 작성한다.
    3. useStateuseReducer로 바꾼다.
  • reducer를 사용하면 코드를 조금 더 작성해야 하지만 디버깅과 테스트에 도움이 된다.
  • reducer는 반드시 순수해야한다.
  • 각 action은 단일 사용자 상호작용을 설명해야한다.
  • 변이 스타일로 reducer를 작성하려면 Immer를 사용하자.

React 공식 문서

https://react.dev/

React 비공식 번역 문서

https://react-ko.dev/

MDN

https://developer.mozilla.org/ko/

Wikipedia

https://ko.wikipedia.org/wiki/

profile
함께 일하고 싶어지는 동료, 프론트엔드 개발자입니다.

0개의 댓글

관련 채용 정보