Extracting State Logic into a Reducer

date
2026-02-19
order
5
link
  • Extracting State Logic into a Reducer โ€“ React

You will learn

  • reducer ํ•จ์ˆ˜๋ž€ ๋ฌด์—‡์ธ์ง€
  • useState ๋ฅผ useReducer๋กœ ๋ฆฌํŒฉํ† ๋ง ํ•˜๋Š”๋ฒ•
  • ์–ธ์ œ reducer๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”์ง€
  • ์ข‹์€ reducer๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•

state ๋กœ์ง์„ Reducer๋กœ ํ†ตํ•ฉํ•˜๊ธฐ

์ปดํฌ๋„ŒํŠธ์˜ state๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋กœ์ง์ด ์—ฌ๋Ÿฌ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— ๋ถ„์‚ฐ๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ ๊ด€๋ฆฌํ•˜๊ธฐ์— ๋„ˆ๋ฌด ๋ณต์žกํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ state ์—…๋ฐ์ดํŠธ ๋กœ์ง์„ ์ปดํฌ๋„ŒํŠธ ๋ฐ–์˜ ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๋กœ ๋ชจ๋‘ ๋ชจ์•„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด ํ•จ์ˆ˜๋ฅผ reducer๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

์ปดํฌ๋„ŒํŠธ์˜ ๋ณต์žก์„ฑ์ด ์ฆ๊ฐ€ํ•˜๋ฉด์„œ ์ปดํฌ๋„ŒํŠธ์˜ ๊ฐ state๋“ค์ด ์–ด๋–ป๊ฒŒ ์—…๋ฐ์ดํŠธ ๋˜๋Š”์ง€ ํ•œ๋ˆˆ์— ํŒŒ์•…ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์›Œ ์ง„๋‹ค.

์˜ˆ๋ฅผ๋“ค์–ด TaskApp ์ปดํฌ๋„ŒํŠธ์—์„œ task state๋ฅผ ์—…๋ฐ์ดํŠธ ํ•˜๋Š” ๋กœ์ง์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž :

  • ํ•  ์ผ์„ ์ถ”๊ฐ€ํ•˜๋Š” handleAddTask
  • ํ•  ์ผ์„ ์ˆ˜์ •ํ•˜๋Š” handleChangeTask
  • ํ•  ์ผ์„ ์ œ๊ฑฐํ•˜๋Š” handleDeleteTask

์ด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋“ค์€ setTask๋ผ๋Š” state ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ํ˜ธ์ถœํ•  ๊ฒƒ์ด๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ด๋Ÿฌํ•œ ๋กœ์ง์˜ ์ฝ”๋“œ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง„๋‹ค.

์ด์ œ reducer๋ฅผ ๋„์ž…ํ•˜๋ฉด ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ์˜ˆ๋ฐฉํ•˜๊ณ  ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์•„๋ž˜์™€ ๊ฐ™์€ ๊ณผ์ •์„ ํ†ตํ•ด ๋ชจ๋“  state ๋ณ€๊ฒฝ ๋กœ์ง์„ reducer๋ผ๋Š” ํ•˜๋‚˜์˜ ์ฝ”๋“œ๋กœ ๋ญ‰์น  ์ˆ˜ ์žˆ๋‹ค.

  1. Move : state๋ฅผ ์„ค์ •(set)ํ•˜๋Š” ๋ฐฉ์‹์—์„œ action์„ ์ „๋‹ฌ(dispatch)ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ „ํ™˜
  2. Write : reducer ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑ
  3. Use : ์ปดํฌ๋„ŒํŠธ์—์„œ reducer๋ฅผ ์‚ฌ์šฉ

1. Move : set state์—์„œ action dispatch๋กœ ์ „ํ™˜ํ•˜๊ธฐ

ํ˜„์žฌ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋“ค์€ ์ด๋ ‡๊ฒŒ ์ง์ ‘์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ํ–‰๋™ํ•ด์•ผ ํ•˜๋Š”์ง€ ํŠน์ •ํ•˜๊ณ  ์žˆ๋‹ค. ์ฆ‰ state๋ฅผ ์„ค์ •(set)ํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

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

Reducer๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์€ ์ด์™€ ์กฐ๊ธˆ ๋‹ค๋ฅด๋‹ค. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ง์ ‘ state๋ฅผ ์„ค์ •ํ•˜๋Š” ๋Œ€์‹ , ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐฉ๊ธˆ ์–ด๋–ค ํ–‰๋™์„ ํ–ˆ๋Š”์ง€๋ฅผ ํŠน์ •ํ•˜๊ณ  ์ด์— ๋งž๋Š” action์„ ๋ณด๋‚ธ๋‹ค(dispatch).

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 ์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. action์€ ์ผ๋ฐ˜์ ์ธ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ์ฒด๋ผ์„œ ํ•„์ˆ˜์ ์ธ ํ•„๋“œ์™€ ๊ฐ™์€ ๊ทœ์น™์€ ์—†๋‹ค. ํ•˜์ง€๋งŒ ๊ธฐ๋Šฅ์„ ์ œ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ type ๊ณผ ๊ฐ™์€ ํ•„๋“œ๋ฅผ ํ†ตํ•ด ์ ์–ด๋„ ์–ด๋–ค ํ–‰๋™์„ ํ–ˆ๋Š”์ง€๋ฅผ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์†Œํ•œ์˜ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

2. Write : reducer ํ•จ์ˆ˜ ์ž‘์„ฑํ•˜๊ธฐ

์ด์ œ state ๋กœ์ง์„ ๋ชจ์•„๋‘” reducer ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. reducer ํ•จ์ˆ˜๋Š” ๋‘ ๊ฐœ์˜ ์ธ์ž๋ฅผ ๋ฐ›๋Š”๋ฐ, ํ˜„์žฌ state์™€ action ๊ฐ์ฒด์ด๋‹ค.

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

๋ฆฌ์•กํŠธ๋Š” state๋ฅผ reducer๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์„ค์ •(set)ํ•  ๊ฒƒ์ด๋‹ค.

์œ„์˜ ์˜ˆ์‹œ์— ๋งž๋Š” reducer๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์ด๋ ‡๊ฒŒ ๋œ๋‹ค :

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

์ธ์ž๋กœ ์ „๋‹ฌ๋ฐ›์€ state์ธ tasks๋ฅผ ๋ฐ›์•„ ์ •ํ•ด์ง„ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์ƒˆ๋กœ์šด tasks๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

3. Use : ์ปดํฌ๋„ŒํŠธ์—์„œ reducer๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ

์›๋ž˜ useState ํ›…์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๋Š” ์ด๋Ÿด๊ฒƒ์ด๋‹ค :

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

๊ทธ๋ฆฌ๊ณ  ์ด setTasks๋ฅผ ๊ฐ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ํ˜ธ์ถœํ•˜์—ฌ state๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š”๊ฒŒ ๊ธฐ์กด์˜ ๋ฐฉ์‹์ด์—ˆ๋‹ค.

useReducer ํ›…์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๋Š” ์ด๋ ‡๊ฒŒ ๋œ๋‹ค :

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

setTasks ํ•จ์ˆ˜ ๋Œ€์‹  dispatch ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , ์ด๊ฑธ ์‚ฌ์šฉํ•˜์—ฌ reducer์—๊ฒŒ state ๋ณ€๊ฒฝ์˜ ์ž์„ธํ•œ ๋‚ด์šฉ์„ ์ „๋‹ฌํ•œ๋‹ค.

useState์™€ useReducer ๋น„๊ต

  • ์ฝ”๋“œ๋Ÿ‰ : useState๋ฅผ ํ†ตํ•ด state๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ, ์ผ์ผ์ด setState๋กœ state ๋ณ€๊ฒฝ ๋‚ด์šฉ์„ ์ง์ ‘ ์ž‘์„ฑํ•ด์•ผํ•˜๋Š”๋ฐ, ๊ทธ๋ž˜์„œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์˜ ์ข…๋ฅ˜๊ฐ€ ๋งŽ์„์ˆ˜๋ก ์ฝ”๋“œ๊ฐ€ ๋Š˜์–ด๋‚œ๋‹ค. ํ•˜์ง€๋งŒ useReducer๋กœ state๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋Š˜์–ด๋‚˜๋„ ๋งŽ์€ ์ฝ”๋“œ๊ฐ€ ์ƒˆ๋กœ ์ž‘์„ฑ๋  ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • ๊ฐ€๋…์„ฑ : useReducer๋Š” ์–ด๋–ป๊ฒŒ์™€ ๋ฌด์—‡์˜ ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ฒŒ ํ•ด์ฃผ์–ด ์ปดํฌ๋„ŒํŠธ์˜ ์ฝ”๋“œ์— ๊ฐ€๋…์„ฑ์„ ๋งค์šฐ ๋†’์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค.
  • ๋””๋ฒ„๊น… : useState์‚ฌ์šฉ์ค‘ ๋ฒ„๊ทธ๋ฅผ ๋ฐœ๊ฒฌํ•œ๋‹ค๋ฉด, ์ข…์ข… ์ •ํ™•ํžˆ ์–ด๋””์„œ, ์™œ state ๋ฅผ ์ž˜๋ชป ์„ค์ •(set)ํ–ˆ๋Š”์ง€ ์ฐพ๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ reducer ๋ฅผ ์‚ฌ์šฉํ•ด ๋ชจ๋“  state ๋ณ€๊ฒฝ์„ ํ•œ๊ณณ์— ๋ชจ์•„๋‘”๋‹ค๋ฉด, reducer ํ•จ์ˆ˜ ์•ˆ์— type๊ณผ ์ „๋‹ฌ ๋ฐ›์€ state๋ฅผ ์ถœ๋ ฅํ•ด๋ณด๋Š” ๋“ฑ์˜ ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋งŒ์œผ๋กœ ๋ฒ„๊ทธ๋ฅผ ์ถ”์ ํ•˜๊ธฐ ์‰ฌ์›Œ์ง„๋‹ค.
  • ํ…Œ์ŠคํŒ… : reducer๋Š” ์ปดํฌ๋„ŒํŠธ์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋‹ค. ๊ทธ๋ž˜์„œ state ๋ณ€๊ฒฝ ๋กœ์ง์ด ๋ณต์žกํ•˜์—ฌ ๋”ฐ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.

reducer ์ž‘์„ฑ ๊ทœ์น™

  • ์ˆœ์ˆ˜ํ•ด์•ผ ํ•œ๋‹ค. reducer๋„ ์–ด์จ‹๋“  state ๋ณ€๊ฒฝ ํ•จ์ˆ˜์ด๊ณ , ๊ทธ๋ž˜์„œ ๋ Œ๋”๋ง ์ค‘์— ์‹คํ–‰๋œ๋‹ค. ๋ Œ๋”๋ง ๋‹จ๊ณ„์—์„œ ์‹คํ–‰๋˜๋Š” ๋ชจ๋“  ์ฝ”๋“œ๋Š” ์ˆœ์ˆ˜ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์›์น™์„ ๋”ฐ๋ผ์•ผ ํ•œ๋‹ค.
  • ๋ชจ๋“  action์€ ๋‹จ ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž ํ–‰๋™์„ ์„ค๋ช…ํ•ด์•ผ ํ•œ๋‹ค. ์‹ฌ์ง€์–ด ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์ด ์žˆ๋”๋ผ๋„. ์˜ˆ๋ฅผ๋“ค์–ด ์‚ฌ์šฉ์ž๊ฐ€ "Reset" ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ๋‹ค๋ฉด, ๊ทธ๊ฒŒ ์—ฌ๋Ÿฌ๊ฐœ์˜ ํ•„๋“œ์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋”๋ผ๋„ ํ•˜๋‚˜์˜ action ์ด์–ด์•ผ ํ•œ๋‹ค.

์˜ˆ์‹œ ๋ฌธ์ œ

4. useReducerํ›… ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ธฐ

//App.js
export default function Messenger() {
  const [state, dispatch] = useReducer(messengerReducer, initialState);
  const message = state.messages[state.selectedId];
  const contact = contacts.find((c) => c.id === state.selectedId);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedId={state.selectedId}
        dispatch={dispatch}
      />
      <Chat
        key={contact.id}
        message={message}
        contact={contact}
        dispatch={dispatch}
      />
    </div>
  );
}

const contacts = [
  {id: 0, name: 'Taylor', email: 'taylor@mail.com'},
  {id: 1, name: 'Alice', email: 'alice@mail.com'},
  {id: 2, name: 'Bob', email: 'bob@mail.com'},
];
//messengerReducer.js
export const initialState = {
  selectedId: 0,
  messages: {
    0: 'Hello, Taylor',
    1: 'Hello, Alice',
    2: 'Hello, Bob',
  },
};

export function messengerReducer(state, action) {
  switch (action.type) {
    case 'changed_selection': {
      return {
        ...state,
        selectedId: action.contactId,
      };
    }
    case 'edited_message': {
      return {
        ...state,
        messages: {
          ...state.messages,
          [state.selectedId]: action.message,
        },
      };
    }
    case 'sent_message': {
      return {
        ...state,
        messages: {
          ...state.messages,
          [state.selectedId]: '',
        },
      };
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}
//MyReact.js
import { useState } from 'react';

export function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  // ???

  return [state, dispatch];
}

์—ฌ๊ธฐ์„œ MyReact.js ํŒŒ์ผ์„ ์™„์„ฑํ•ด useReducer ํ›…์„ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

์ผ๋‹จ useReducer๋Š” state์™€ dispatch๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. state๋Š” ์ด๋ฏธ ์ž‘์„ฑ๋˜์–ด ์žˆ์œผ๋‹ˆ ๋‚˜๋จธ์ง€ ๋ฐ˜ํ™˜๊ฐ’์ธ dispatch ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•ด์„œ ์™„์„ฑํ•ด์•ผ ํ•œ๋‹ค.

  1. dispatch๋Š” action๊ฐ์ฒด๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.
//MyReact.js
import { useState } from 'react';

export function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action){
  //...
  }

  return [state, dispatch];
}
  1. reducer ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด messengerReducer๋ฅผ ์ฃผ์ž… ๋ฐ›์•˜๋Š”๋ฐ, messengerReducer๋Š” action์˜ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
//MyReact.js
import { useState } from 'react';

export function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action){
  const temp = reducer(state,action);
  //...
  }

  return [state, dispatch];
}
  1. temp๋Š” ์—…๋ฐ์ดํŠธ๋œ state๋‹ค. ์ด์ œ ์ด๊ฑธ setState() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด state๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ๋ฆฌ๋ Œ๋”๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค.
//MyReact.js
import { useState } from 'react';

export function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action){
  const temp = reducer(state,action);
  setState(temp);
  }

  return [state, dispatch];
}