흰 스타렉스에서 내가 내리지

상태 로직을 리듀서로 추출하기 본문

React

상태 로직을 리듀서로 추출하기

주씨. 2024. 5. 19. 16:09
728x90

 

많은 이벤트 핸들러에 걸쳐 많은 상태 업데이트가 분산되는 컴포넌트는 복잡해질 수 있습니다.

이러한 경우에는 컴포넌트 외부에 모든 상태 업데이트 로직을 단일 함수인 "리듀서"로 통합할 수 있습니다.

이벤트 핸들러는 사용자 "액션"만을 지정하기 때문에 간결해집니다.

파일의 맨 아래에서 리듀서 함수는 각 액션에 대한 상태 업데이트 방식을 지정합니다!

 

 

리듀서를 사용하는 경우 상태를 설정하기 위한 이벤트 핸들러 대신 액션을 디스패치하여 "사용자가 방금 무엇을 했는지"를 지정합니다. (상태 업데이트 로직은 다른 곳에 있습니다!)

따라서 이벤트 핸들러를 통해 "작업 추가/변경/삭제" 액션을 디스패치하는 대신 "작업을 추가/변경/삭제했다"라고 명시합니다.

이는 사용자의 의도를 더 명확하게 나타냅니다.

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에 전달하는 객체를 "액션"이라고 합니다

 

function handleDeleteTask(taskId) {
  dispatch(
    // "액션" 객체:
    {
      type: 'deleted',
      id: taskId,
    }
  );
}

 

 

 

 

 

 

# App.js

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

 

리듀서 내부에는 switch 문을 사용하는 것이 관례입니다

 

 

# AddTask.js

import { useState } from 'react';

export default function AddTask({ onAddTask }) {
  const [text, setText] = useState('');
  return (
    <>
      <input
        placeholder="Add task"
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={() => {
        setText('');
        onAddTask(text);
      }}>Add</button>
    </>
  )
}

 

 

# TaskList.js

import { useState } from 'react';

export default function TaskList({
  tasks,
  onChangeTask,
  onDeleteTask
}) {
  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>
          <Task
            task={task}
            onChange={onChangeTask}
            onDelete={onDeleteTask}
          />
        </li>
      ))}
    </ul>
  );
}

function Task({ task, onChange, onDelete }) {
  const [isEditing, setIsEditing] = useState(false);
  let taskContent;
  if (isEditing) {
    taskContent = (
      <>
        <input
          value={task.text}
          onChange={e => {
            onChange({
              ...task,
              text: e.target.value
            });
          }} />
        <button onClick={() => setIsEditing(false)}>
          Save
        </button>
      </>
    );
  } else {
    taskContent = (
      <>
        {task.text}
        <button onClick={() => setIsEditing(true)}>
          Edit
        </button>
      </>
    );
  }
  return (
    <label>
      <input
        type="checkbox"
        checked={task.done}
        onChange={e => {
          onChange({
            ...task,
            done: e.target.checked
          });
        }}
      />
      {taskContent}
      <button onClick={() => onDelete(task.id)}>
        Delete
      </button>
    </label>
  );
}

 

 

 

 

https://wikidocs.net/203666

 

05. 상태 관리

> 원문: [https://react.dev/learn/managing-state](https://react.dev/learn/managing-state) [TOC] > 중급…

wikidocs.net

 

 

 

 

Immer를 사용하여 간결한 리듀서 작성하기

 

일반 상태 업데이트와 마찬가지로 리듀서를 더 간결하게 작성할 수 있는 Immer 라이브러리를 사용할 수 있습니다.

여기서 useImmerReducer를 사용하면 push 또는 arr\[i\] = 할당을 사용하여 상태를 직접 변경할 수 있습니다.

 

import { useImmerReducer } from 'use-immer';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

function tasksReducer(draft, action) {
  switch (action.type) {
    case 'added': {
      draft.push({
        id: action.id,
        text: action.text,
        done: false,
      });
      break;
    }
    case 'changed': {
      const index = draft.findIndex((t) => t.id === action.task.id);
      draft[index] = action.task;
      break;
    }
    case 'deleted': {
      return draft.filter((t) => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

export default function TaskApp() {
  const [tasks, dispatch] = useImmerReducer(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}
      />
    </>
  );
}

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