Reducer

숭이·2022년 4월 11일
0

Reducer란

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

useState의 대체 함수입니다. (state, action) => newState의 형태로 reducer를 받고 dispatch 메서드와 짝의 형태로 현재 state를 반환합니다. (리액트 공식문서)

다시말해 reducer 함수는 state 객체와 action 객체를 인자로 받아서 새로운 state 객체를 반환하는 함수입니다.

그리고 dispatch 함수는 컴포넌트 내에서 상태 변경을 일으키기 위해서 사용되는데 인자로 reducer 함수에 넘길 action 객체를 받습니다.

action 객체는 관행적으로 어떤 부류의 행동인지를 나타내는 type 속성과 해당 행동과 관련된 데이터를 담고 있습니다.

다시 말해, 컴포넌트에서 dispatch 함수에 action을 던지면, reducer 함수가 이 action에 따라서 state를 변경해줍니다.

다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우나 다음 state가 이전 state에 의존적인 경우에 보통 useState보다 useReducer를 선호합니다.

또한 useReducer는 자세한 업데이트를 트리거 하는 컴포넌트의 성능을 최적화할 수 있게 하는데, 이것은 콜백 대신 dispatch를 전달 할 수 있기 때문입니다.

복잡한 정적 로직 예

import React, { useReducer } from "react";

function Counter({ step = 1, min = 0, max = 10 }) {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <p>
        단계: {step}, 최소: {min}, 최대: {max}
      </p>
      <h2>{state.count}</h2>
      <button onClick={() => dispatch({ type: "INCREMENT", step, max })}>
        증가
      </button>
      <button onClick={() => dispatch({ type: "DECREMENT", step, min })}>
        감소
      </button>
      <button onClick={() => dispatch({ type: "RANDOM", min, max })}>
        무작위
      </button>
      <button onClick={() => dispatch({ type: "RESET" })}>초기화</button>
    </>
  );
}
const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      return state.count < action.max
        ? { count: state.count + action.step }
        : state;
    case "DECREMENT":
      return state.count > action.min
        ? { count: state.count - action.step }
        : state;
    case "RESET":
      return initialState;
    case "RANDOM":
      return {
        count:
          Math.floor(Math.random() * (action.max - action.min)) + action.min,
      };
    default:
      throw new Error("Unsupported action type:", action.type);
  }
}

reducer를 사용하지 않는 다면, 각 버튼마다 onclick = {() => {}}에 로직을 다 써줘야 합니다.

행동에 따라 동일한 값이 다양한 방법으로 변화하는 경우 reducer를 쓰면 관리가 쉽고 일목요연하게 정리할 수 있다는 장점이 있습니다.

다시말해 컴포넌트 코드는 크게 복잡해지지 않고(dispatch의 파라미터만 변경) 새로운 타입의 action 함수를 정의하면 됩니다.

초기화 지연

reducer를 정의할 때 init 함수를 세번째 인자로 전달하는 방법으로 state를 조금 지연해서 생성할 수도 있습니다.

이것은 reducer 외부에서 초기 state를 계산하는 로직을 추출할 수 있도록 합니다.

또한, 어떤 행동에 대한 대응으로 나중에 state를 재설정하는 데에도 유용합니다.

function init(initialCount) {
  return {count: initialCount};
}


function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
}

callback 전달 피하기

모든 레벨의 컴포넌트 트리를 통해 콜백을 수동으로 전달하는 것을 좋아하지 않는다는 것을 발견했습니다. 더 명백하지만 마치 “배관”이 많은 것처럼 느껴질 수 있습니다.

큰 컴포넌트 트리에서 권장되는 대안은 context를 통해 useReducer에서 dispatch 함수 컴포넌트를 전달하는 것입니다. (React 공식문서)

const TodosDispatch = React.createContext(null);

function TodosApp() {
  // 주의: `dispatch`는 다시 렌더링 간에 변경되지 않습니다
  const [todos, dispatch] = useReducer(todosReducer);

  return (
    <TodosDispatch.Provider value={dispatch}>
      <DeepTree todos={todos} />
    </TodosDispatch.Provider>
  );
}

TodosApp 내의 트리에 있는 모든 자식은 dispatch 기능을 사용하여 TodosApp에 작업을 전달할 수 있습니다.

이것은 유지 보수 관점에서 더 편리하고 (콜백을 계속 전달할 필요가 없음) 콜백 문제를 모두 방지합니다.

dispatch context는 변경되지 않음으로 이를 읽는 컴포넌트는 애플리케이션 state가 필요하지 않은 한 다시 렌더링할 필요가 없습니다.

0개의 댓글