본문 바로가기

웹/React

[React] reducer와 useReducer 사용하기

반응형

 

 

 

이번에는 reducer와 useReducer를 공부해보자.

 

컴포넌트가 복잡해지면 컴포넌트의 state들이 업데이트 되는 것들을 한번에 파악하기 어려울 수 있다. 예를 들어서 어떠한 배열인 state가 있고,  이 state에 요소를 추가하거나 제거하거나 업데이트 하는 세가지의 이벤트 헨들러(이 녀석들은 모두상태 변화 함수를 통해서 state를 변경)가 있다고 한다면 여기저기 흩어져 있는 이벤트 헨들러를 관리하기가 어려울 것이다. 또한 흩어져 있다 보니 코드의 양도 길어질 것이다. 

 

코드의 양을 줄이고 모든 헨들러에 접근하기 쉽게 한곳에 모으기 위해서 reducer라는 단일 함수를 사용한다.

 

즉, reducer는 state를 관리하는 또 다른 방법인 것이다. 

 

3단계를 거쳐서 state들을 reducer를 통해 관리해 보자.

 

이해를 위해서 1단계 부터 3 단계 까지 읽은 후 다시 3단계에서 1단계까지 거꾸로 읽으면 좋다.

 

쉽게 알아보기 위해서 예시 코드를 사용하자.

function handleAddTask(text) {
  setTasks([
    ...tasks,
    {
      id: nextId++,
      text: text,
      done: false,
    },
  ]);
}

function handleChangeTask(task) {
  setTasks(
    tasks.map((t) => {
      if (t.id === task.id) {
        return task;
      } else {
        return t;
      }
    })
  );
}

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

1. 먼저 setState를 통해서 state를 설정하던 것에서 action이라는 객체의 전달로 바꾸기

 

action이라는 객체는 설명할 것이다. 생각보다 별거 아니다.

 

위 코드를 보면 3개의 이벤트 핸들러 함수가 모두 상태 변화 함수를 호출하여 state를 변화 시키고 있다.

 

앞으로 우리가 할 것은 사용자가 Add를 누르면  handleAddTask가 호출되게 하고 task를 토글하거나 Save를 누르면 handleChangeTask를 호출하고, Delete를 누르면 handleDeleteTask를 호출하게 할 것이다.

 

기존에는 state를 직접 설정하였지만 이제부터는 action이라는 객체를 전달하여 사용하가 한 일을 전달하는 것이다.

 

다시 말해서 state를 설정하는 대신 state를 추가하거나 변경하거나 삭제하는 작동을(action을) 전달하는 것이다. 그래서 다음과 같이 코드를 수정할 수 있다.

 

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,
  });
}

 

 

여기서 dispatch 안에 들어있는 객체가 action이라는 객체다. ( dispatch 함수는 뒤에 3에서 설명)

 

객체안에 수행하는 작업을 type으로 전달하였고 작업에 필요한 멤버들을 같이 전달하였다.


2. reuducer 함수 작성하기

reducer 함수에서는 이전에 3가지 이벤트 헨들러에서 작성했었던 state들을 관리하는 로직을 작성한다.

 

reducer 함수는 다음과 같이 작성한다.

function yourReducer(state, action) {
  // return next state for React to set
}

 

reducer 함수는 2개의 매개변수를 가진다. 첫번째 매개변수는 현재의 state 값이고, 두번째 매개변수는 action객체이다.

 

그리고 이 함수는 변화된 state의 값을 반환한다.

 

이를 통해서 React는 reducer 함수가 반환한 값을 state의 값으로 설정할 것이다.

 

reducer 함수는 다음과 같이 작성하고 내부에 내용을 채우면 된다. 

 

앞에서 우리는 action 객체 내부에 type이라는 멤버에 수행한 동작을 적어두었기 때문에 action의 type에 따라 작동하도록 만들어야 할 것이다.

 

여기서는 if문을 사용할 수도 있고  switch case문을 사용할 수도 있다.

 

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);
  }
}

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

마지막으로는 컴포넌트에 reducer 함수를 연결해야 한다.

 

이때는 React에서 useReducer Hook를 import 한다.

 

그리고 기존에  사용했던 useState 대신 useReducer 로 바꿔준다.

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

 

==========>

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

 

useReducer Hook은 useState 와 비슷하다. 초기의 state 값을 전달해야되고, 그 결과로 state의 값과 state를 설정하는 함수를 반환한다. 

 

차이점이 있다면 useReducer 훅은 2개의 인자를 받는다는 것이다.

 

첫번째로 reducer 함수를 받고, 두번째로 초기 state의 값을 받는다.

그리고 반환값으로는 state의 값과 dispatch 함수를 반환한다.

 

여기서 dispatch 함수는 사용자의 action을 reducer에 전달해주는 함수이다.

 

앞에서 살펴봤던것처럼 이벤트 핸들러 안에서 상태변화 함수를 직접 사용한 대신 dispatch를 통해서 action을 redicer에 전달하도록 바꿔주었다. 

 

따라서 dispatch 함수에 action객체를 넣어서 호출하게 되면 자동으로 useReducer에서 첫번재 인자로 넘겨준 tasksReducer 함수가 실행된다.

 

이를 통해서 Full code를 작성하였다.

 

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

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}
      />
    </>
  );
}

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);
    }
  }
}

let nextId = 3;
const initialTasks = [
  {id: 0, text: 'Visit Kafka Museum', done: true},
  {id: 1, text: 'Watch a puppet show', done: false},
  {id: 2, text: 'Lennon Wall pic', done: false},
];

 

reducer 함수가 보기 싫다면 파일로 분리하는 것도 가능하다.

 

 

그리고 reducer 를 사용할 때  ★주의해야 할 점★ 이 있다.

 

reducer 함수가 반환하는 값은 변결될 state의 값이다. 따라서 state의 값을 직접 변경해서는 안된다.

 

useState에서 state의 값을 상태 변화 함수를 통해서 변경하였지 직접 변경하지 않은 것과 같은 의미이다.

 

이를 순수하다고 한다.

 

따라서 입력과 결과가 항상 같아야 한다.

 

 

반응형

' > React' 카테고리의 다른 글

[React] useMemo 사용하기  (0) 2024.08.10
[React] useEffect 사용하기  (0) 2024.07.26
[React] React Hooks  (0) 2024.07.18
[React] useRef로 컴포넌트의 변수 생성하기  (0) 2024.07.18
[React] State로 사용자 입력관리하기2  (0) 2024.07.18