컴포넌트 재사용과 redux 마이그레이션

soo's·2023년 4월 19일
0

Project - To-Do list

목록 보기
4/4

진행상황

이전 포스팅에서, 코드에서 재사용할 수 있음에도 불필요한 컴포넌트를 만들어서 사용한 부분이 있었고, 상태관리를 그냥 useState hook을 이용하다가 props drilling과 중구난방 퍼져있는 state들의 문제가 있다는 것을 정리했었다.

1. 컴포넌트 재사용으로 중복 코드 제거

기존에는 DoneItem과 TodoItem으로 컴포넌트가 나뉘어져 있었다. 하지만 이부분은 기능적으로 함수 하나만 제외하고 모두 같은 역할을 하고 있기 때문에 하나의 컴포넌트로 재사용할 수 있는 부분이었다.
따라서 아래와 같이 리팩토링했다.

// ItemTodo.jsx
import React from "react";
import { useDispatch } from "react-redux";
import { remove_todo, get_done, turn_back } from "../redux/modules/setData";

function ItemTodo({ todo }) {
  const dispatch = useDispatch();

  // 삭제
  const handleRemove = () => {
    if (window.confirm("정말 삭제하시겠습니까?")) {
      dispatch(remove_todo(todo.id));
      alert("삭제되었습니다!");
    } else return;
  };

  // 완료
  const handleDone = () => {
    window.confirm("정말 완료하셨나요?") && dispatch(get_done(todo.id));
  };

  // 취소
  const handleTurnBack = () => {
    dispatch(turn_back(todo.id));
    alert("다시 할 일이 추가됐네요!");
  };

  return todo.isDone ? (
    <>
      <article className="DoneItem box-same1">
        <h2>{todo.title}</h2>
        <p>{todo.content}</p>
        <div className="con-btns">
          <button className="btn-delete btn-original" onClick={handleRemove}>
            삭제
          </button>
          <button className=" btn-original" onClick={handleTurnBack}>
            취소
          </button>
        </div>
      </article>
    </>
  ) : (
    <>
      <article className="TodoItem box-same1">
        <h2>{todo.title}</h2>
        <p>{todo.content}</p>
        <div className="con-btns">
          <button className="btn-delete btn-original" onClick={handleRemove}>
            삭제
          </button>
          <button className="btn-original" onClick={handleDone}>
            완료
          </button>
        </div>
      </article>
    </>
  );
}

export default ItemTodo;

요약하자면 isDone 값에 따라 조건부 렌더링을 할 수 있게 해서 굳이 두 개의 컴포넌트로 나누지 않고 Working과 Done 컴포넌트에서 사용할 수 있게 했다.

//Working.jsx
const Working = () => {
  const dataStore = useSelector((state) => state.setData);

  return (
    <div>
      <h2>Working</h2>
      {dataStore
        .filter((todo) => !todo.isDone)
        .map((todo) => (
          <ItemTodo todo={todo} key={todo.id} />
        ))}
    </div>
  );
};

// Done.jsx
const ListTodo = () => {
  const dataStore = useSelector((state) => state.setData);

  return (
    <div>
      <h2>Done</h2>
      {dataStore
        .filter((todo) => todo.isDone)
        .map((todo) => (
          <ItemTodo todo={todo} key={todo.id} />
        ))}
    </div>
  );
};

이전 코드와 마찬가지로 filter를 통해서 todo의 isDone 값이 true false인지에 따라 todo 배열을 다시 ItemTodo 컴포넌트로 넘겨준다. 따라서 ItemTodo에서는 isDone 값에 따라 조건부 렌더링을 할 수 있게 됐다!

2. useState에서 redux로

기존 코드에서 상태를 useState로 관리하고 있었다. 컴포넌트가 늘어날수록 상태를 사용해야 하는 곳도 많아지고 단순히 상태만 전달하기 위해서 프롭으로 계속 전달하는 props-drilling도 생겼다. 따라서 전역으로 상태를 관리할 수 있는 redux를 사용해서 이 프로젝트를 마이그레이션 하기로 했다.

redux의 덕스 패턴을 사용해서 작성했다.

// action value
const SET_TITLE = "SET_TITLE";
const SET_CONTENT = "SET_CONTENT";
const RESET = "RESET";

// action creators
export const set_title = (e) => {
  return { type: SET_TITLE, payload: e.target.value };
};
export const set_content = (e) => {
  return { type: SET_CONTENT, payload: e.target.value };
};
export const clearTodo = () => {
  return { type: RESET };
};

// 초기값 설정
const initialState = {
  title: "",
  content: "",
  isDone: false,
};

// reducer
const reducer_setTodo = (state = initialState, action) => {
  switch (action.type) {
    case SET_TITLE:
      return { ...state, title: action.payload };
    case SET_CONTENT:
      return { ...state, content: action.payload };
    case RESET:
      return initialState;
    default:
      return state;
  }
};

export default reducer_setTodo;

action value를 사용해서 휴먼 에러를 줄여주고 action creators를 설정하고 reducer 함수에서 이 액션 객체의 type에 상태를 변경해줄 리듀서 함수를 작성한다.

컴포넌트에서는 dispatch를 사용해서 액션 크리에이터(액션 객체 생성해줌)가 생성한 액션 객체를 reducer 함수에게 넘겨서 상태 변화를 일으켜준다.

// CreateTodo
const CreateToDo = () => {
  const dispatch = useDispatch();
  const todoStore = useSelector((state) => state.setTodo);
  const titleInput = useRef();
  const contentInput = useRef();

  const handleTitleChange = (e) => {
    dispatch(set_title(e));
  };

  const handleContentChange = (e) => {
    dispatch(set_content(e));
  };

  const handleAddTodo = () => {
    if (todoStore.title.length < 1) {
      titleInput.current.focus();
      return;
    }
    if (todoStore.content.length < 2) {
      contentInput.current.focus();
      return;
    }
    dispatch(
      create_todo({
        title: todoStore.title,
        content: todoStore.content,
        isDone: false,
        id: uuidv4(),
      })
    );

    dispatch(clearTodo());
    alert("해야 할 일이 생겼어요!");
  };
  ...생략
};

기존에 컴포넌트에서 타고 타서 프롭으로 받아오던 데이터를 전역의 상태로 관리하면서 사용하니 데이터를 사용함에 있어서 굉장히 편리하다. 하지만 액션 크리에이터랑 리듀서 함수, 밸류등을 작성하면서 코드가 조금 길어지는 부분이 없지 않아 있는듯 하다.

0개의 댓글