[React] useState에서 useReducer로 리팩토링하기

박세화·2023년 6월 9일

React JS

목록 보기
5/22

공식문서 참고하여 작성함

1. useState를 dispatch action으로 교체

useState가 state를 setting함으로써 리액트에게 "무엇을 해라" 라고 말해주었다면, useReducer을 사용할 땐 이벤트 핸들러로부터 "유저가 방금 뭘 했는지" 그 액션을 전달(dispatch)할 뿐이다.

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

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

📌 dispatch function은 return value를 가지지 않는다.

2. Reducer function 작성

Reducer functioncurrent state와 action object를 파라미터로 갖고, next state를 반환한다. 그 반환된 next state로 state를 업데이트할 것임.

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

Reducer function이 state인 tasks를 인자로 받기 때문에, tasks를 컴포넌트 외부에 선언해도 된다! 이것이 코드의 indentation level을 줄여줘서 보다 읽기 편한 코드가 됨.
📌 보통 reducer function 안에는 if/else문 대신에 switch 구문을 쓰는 것이 컨벤션이다.

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

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

내가 만들어놓은 Reducer function(tasksReducer)을 useReducer hook에 사용한다.
useReducer은 hook이기 때문에 컴포넌트의 최상단에 위치해야 한다.

  • tasks의 initial value는 initialTasks가 된다.
  • 각 이벤트의 핸들러들이 taksReducer에게 이벤트 타입을 전달하면, tasksReducer가 상태를 업데이트한다.

📌 useReducer은 두 가지 값을 가진 array를 리턴한다.

  • 1) current state. 최초 렌더링 동안, 두 번째 파라미터인 initial 값을 가진다.
  • 2) dispatch function. 상태를 다른 값으로 업데이트하고 리렌더링을 일으킬 수 있도록 해주는 함수

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

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

Reducer function은 아예 다른 파일에 만들어도 상관없다.

0개의 댓글