(9) Todo List 클린 코드로 리팩토링 및 고도화 작업 (코드줄 줄이기, 분할, 필요없는 주석 제거 등)

2023. 6. 10. 21:29Github 프로젝트/todolist반응형웹앱

728x90
반응형
SMALL

이전 시간  (8) Todolist 메인 부분 이외의 상세부분의 대해 CRUD 작성 : https://berkley.tistory.com/12

 

(8) Todolist 메인 부분 이외의 상세부분의 대해 CRUD 작성

지금까지 TodoList CRUD 및 UI 반응형 웹앱, Redux까지 반영을 시켰습니다. Redux 반영에 대해서는 링크 참조 바랍니다. https://berkley.tistory.com/11 (7) Todo List Redux - Local Storage로 state 리팩토링 지금까지 CRUD

berkley.tistory.com

 

안녕하세요. 지난시간에 이어서 소분류 CRUD 적용까지 진행을 하였습니다.

이번 시간에는 지금까지 짠 코드들의 대해 클린 코딩 작업을 진행을 하며, 모듈화를 하고 지금까지 짠 코드들의 대해서 리팩토링 하는 시간을 가지는 것으로 하겠습니다.

 

1. 소스코드 클린코딩 

  • 서식 지정 : 깔끔한 코드를 만들려면 서식이 유지되어야 한다.
  • 변수명 설정 : 코드 분석 시 가독성 있게 한다. (현재 연습용이기 때문에 생략 실무에서는 반드시 명시)
  • 코드 줄 : 가능하면 1개에 모듈에서 500줄 이상 넘기지 말아야한다.
    100줄~200줄이 가장 적당한 코드, 반복되는 코드는 반복문으로 대처, 가급적 같은 결과 값에서 코드가 짧을수록 좋다.
  • 모듈화 : 컴포넌트별로 모듈화 하여 작성한다.
  • 효율성 : 같은 결과 대비 자원을 최대한 적게 쓰는 방식으로 사용한다. 가급적 시간복잡도가 낮은 코드 사용

 

이 밖에 더 자세한 사항은 

클린 코딩 하는 방법에 대해 구글링 하거나

책 : 클린 소프트웨어 (애자일 원칙과 패턴 그리고 실천 방법) by  로보트 C. 마틴 을 참조하여라.

 

 

2. 컴포넌트 별 모듈화하여 코드 쪼개기 

지금은 단순한 프로젝트이기 때문에 300줄 분량의 코드가 나온다.

만일 이보다 기능 추가나 컴포넌트를 추가를 하게 될 경우 500줄 이상의 코드가 나와 유지보수를 하기에는 불편함을 겪는다.

따라서 공통으로 사용되는 state나 메서드를 제외한 나머지 컴포넌트별로 각각 모듈화 하여 코드를 작성하는 습관을 기르고, 클린 코드로 만드는 것이 중요합니다.

이제부터 시작하겠습니다.

 

 

3. 지금 까지 만든 코드 <수정 전> - App.js에 한곳으로 몰아 넣었을 경우

import {
  Button,
  Card,
  CardBody,
  CardHeader,
  CardTitle,
  Form,
  Input,
} from "reactstrap";
import "./App.css";
import { PlusCircle, Trash3 } from "react-bootstrap-icons";
import { useState } from "react";
import update from "immutability-helper";
import { useDispatch, useSelector } from "react-redux";

function App() {
  /**
   * 필요 key:value값
   * title, contents
   * 이 값은
   */
  const todoList = useSelector((state) => state.todoList.array);
  const dispatch = useDispatch();
  const [createInputTitle, setCreateInputTitle] = useState();
  // 갱신모드 설정
  const [isTitleUpdate, setIsTitleUpdate] = useState(false);

  // Todolist title 수정용 state
  const [changeTitle, setChangeTitle] = useState();

  // todolist 소분류 추가 입력 모드 활성화 state
  const [isContentsAdd, setIsContentsAdd] = useState(false);
  const [createInputContents, setCreateInputContents] = useState();

  // todolist 소분류 갱신모드 설정 여부
  const [isContentsUpdate, setIsContentsUpdate] = useState(false);
  // todolist 입력
  const [updateInputContents, setUpdateInputContents] = useState();

  /**
   * 다음은 TodoList 입력용 이벤트 함수
   */
  const createTitleOnchange = (e) => {
    const { name, value } = e.target;
    setCreateInputTitle({
      ...createInputTitle,
      [name]: value,
    });
  };
  /**
   * 입력시 Todolist 추가하는 기능
   */
  const createTitleButton = () => {
    let to = [];
    to.push(...todoList);
    to.push({
      title: createInputTitle?.title,
      contents: [],
    });
    /**
     * dispatch는 todolist.js 의 reducer의 action으로 사용되는 함수로
     * state 값을 변경시켜준다.
     * redux의 state 값은 불변성을 유지하는 것이 목적이다.
     * switch문의 action.type 변수에 따라 return으로 명령어대로 해당 state 값을 변경 시켜준다.
     */
    dispatch({ type: "setTodoList", array: to });
  };

  /**
   * todolist 소분류 추가 작업 입력
   */
  const addContentsOnChange = (e, index) => {
    const { name, value } = e.target;
    setCreateInputContents({ [index]: { [name]: value } });
    console.log(createInputContents);
  };
  /**
   *  Todolist 소분류 추가하는 기능
   */
  const createContentsButton = (todo, index) => {
    let tmp = todo;
    tmp?.contents.push(createInputContents[index].contents);
    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        $merge: {
          [index]: tmp,
        },
      }),
    });
  };

  // 수정용 메서드
  const updateTitlOnChange = (e, index) => {
    const { name, value } = e.target;
    setChangeTitle({ [index]: { [name]: value } });
    console.log(changeTitle);
  };

  // 수정 부분 입력 후 갱신 메서드
  const updateTitle = (index) => {
    /**
     * 불변성 유지하면서 갱신 시킬 수 있습니다.
     */
    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        [index]: {
          title: { $set: changeTitle[index].title },
        },
      }),
    });
  };

  /**
   * 소분류용 갱신 메서드 입력
   */
  const updateContentsOnChange = (e, index, tcIndex) => {
    const { name, value } = e.target;
    setUpdateInputContents({ [index]: { [tcIndex]: { [name]: value } } });
    console.log(updateInputContents);
  };

  /**
   * 소분류용 갱신 메서드 수행 = state 값 반영
   */
  const updateContents = (index, tcIndex) => {
    /**
     * 불변성 유지하면서 갱신 시킬 수 있습니다.
     */
    let contents = updateInputContents[index][tcIndex].contents;
    let to = todoList[index];
    to.contents[tcIndex] = contents;

    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        [index]: { $set: to },
      }),
    });
  };

  // 삭제 관련 메소드 (제목)
  const removeTitle = (index) => {
    /**
     * 불변성 유지하면서 삭제 시킬 수 있습니다.
     */
    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        $splice: [[index, 1]],
      }),
    });
  };

  // 삭제 관련 메소드 (소분류)
  const removeContents = (index, tcIndex) => {
    let to = update(todoList[index].contents, {
      $splice: [[tcIndex, 1]],
    });

    console.log(to);

    dispatch({
      type: "setTodoList",
      array: update(todoList, {
          [index]: {
            contents: {$set: to},
          },
      }),
    });

    // console.log(tcIndex)
  };

  return (
    <div className="my-2 background-container">
      <Card className="my-2 container">
        <CardHeader className="header-container">
          To-do list 반응형 웹 개발
        </CardHeader>
        <CardBody>
          <CardTitle tag="h2">To-do list</CardTitle>
          <div className="todo">
            {
              // 다음은 각각 데이터를 불려올 때 map을 주로 사용합니다.
              // todoList에 저장된 state 값을
              // 유사 forEach문처럼 todo를 이용하여 출력하고,
              // 상위 div에 key값을 설정하기 위해 index 값을 집어 넣어야 합니다.
              todoList?.map((todo, index) => (
                <div key={index} className="todoContainer">
                  <div className="todoTitle">
                    {isTitleUpdate ? (
                      <div>
                        <Input
                          name="title"
                          defaultValue={todo?.title}
                          onChange={(e) => updateTitlOnChange(e, index)}
                        />
                        <Button onClick={() => updateTitle(index)}>
                          todolist수정
                        </Button>
                        <Button
                          onClick={() => setIsTitleUpdate(!isTitleUpdate)}
                        >
                          취소
                        </Button>
                      </div>
                    ) : (
                      <div>
                        {todo?.title}
                        <Button
                          onClick={() => setIsTitleUpdate(!isTitleUpdate)}
                        >
                          수정
                        </Button>{" "}
                        <Button
                          onClick={() => setIsContentsAdd(!isContentsAdd)}
                        >
                          <PlusCircle />
                        </Button>
                        <Button onClick={() => removeTitle(index)}>
                          <Trash3 />
                        </Button>
                      </div>
                    )}
                  </div>

                  {isContentsAdd && (
                    <>
                      {"소분류 추가용 Input : "}
                      <Input
                        name="contents"
                        // defaultValue={}
                        onChange={(e) => addContentsOnChange(e, index)}
                      />
                      <Button onClick={() => createContentsButton(todo, index)}>
                        추가
                      </Button>
                    </>
                  )}
                  {todo?.contents?.map((tc, tcIndex) => (
                    <div key={tcIndex} className="todoContents">
                      {isContentsUpdate ? (
                        <div>
                          -
                          <Input
                            name="contents"
                            defaultValue={tc}
                            onChange={(e) =>
                              updateContentsOnChange(e, index, tcIndex)
                            }
                          />
                          <Button
                            onClick={() => updateContents(index, tcIndex)}
                          >
                            todolist수정
                          </Button>
                          <Button
                            onClick={() =>
                              setIsContentsUpdate(!isContentsUpdate)
                            }
                          >
                            취소
                          </Button>
                        </div>
                      ) : (
                        <div>
                          - {tc}
                          <Button
                            onClick={() =>
                              setIsContentsUpdate(!isContentsUpdate)
                            }
                          >
                            수정
                          </Button>{" "}
                          <Button
                            onClick={() => removeContents(index, tcIndex)}
                          >
                            <Trash3 />
                          </Button>
                        </div>
                      )}
                    </div>
                  ))}
                </div>
              ))
            }
          </div>

          {/**
           *
           */}
          <Form className="addGroup">
            <Input
              className="addInput"
              name="title"
              defaultValue={createInputTitle}
              onChange={createTitleOnchange}
            />
            <Button className="addButton" onClick={createTitleButton}>
              추가
            </Button>
          </Form>
        </CardBody>
      </Card>
    </div>
  );
}

export default App;

 

이 경우. 매우 긴 코드를 볼 수 있습니다.

 

 

4. 분할 대상

다음은 각 컴포넌트별로 분할 대상의 대해서 설명 드리겠습니다

 

아래 화면과 같이 각 컴포넌트별로 어떻게 구성 되어있는지의 대해 그림으로 참조 하시고, 분할 대상별로 나눠 진행 하겠습니다.

 

 

<분할 예정 코드>

1200px 초과 기준 분할 설명

 

 

필자는 아래와 같이 

component 디렉토리 를 생성 후

create <분할 대상2>

main <분할 대상1> 로 따로 디렉토리를 생성하고,

각각 쪼개서 진행 하고자 합니다..

 

 

(4-1) 분할 대상 1

<분할 방법>

1. 먼저 빨간색 컴포넌트를 따로 분할 합니다. 이때, 분할 할때 해당 컴포넌트 안에서만 사용하는 state와 메소드를 가져와 사용합니다.

 

./component/main/index.js에

App.js의 todoList?.map((todo, index) =>  {/* 이부분 기존 코드를 ./component/main/index.js 파일로 이동 */ }

기존에 쓰던 state와 기능들을 옮기고 안에 CreateTitle 컴포넌트를 만들어서 사용합니다.

 

 

아래와 같이 분할하여 사용 가능합니다.

 

<App.js>

import {
  Card,
  CardBody,
  CardHeader,
  CardTitle,
} from "reactstrap";
import "./App.css";
import { useSelector } from "react-redux";

import CreateTitle from "./component/create/index";
import TodoMain from "./component/main/index";

function App() {
  const todoList = useSelector((state) => state.todoList.array);

  return (
    <div className="my-2 background-container">
      <Card className="my-2 container">
        <CardHeader className="header-container">
          To-do list 반응형 웹 개발
        </CardHeader>
        <CardBody>
          <CardTitle tag="h2">To-do list</CardTitle>
          <div className="todo">
            {
              // 다음은 각각 데이터를 불려올 때 map을 주로 사용합니다.
              // todoList에 저장된 state 값을
              // 유사 forEach문처럼 todo를 이용하여 출력하고,
              // 상위 div에 key값을 설정하기 위해 index 값을 집어 넣어야 합니다.
              todoList?.map((todo, index) => (
                <TodoMain index={index} todo={todo} todoList={todoList} />
              ))
            }
          </div>
          <CreateTitle />
        </CardBody>
      </Card>
    </div>
  );
}

export default App;

 

 

< ./component/main/index.js >

import React, { useState } from "react";
import { PlusCircle, Trash3 } from "react-bootstrap-icons";
import { useDispatch } from "react-redux";
import { Button, Input } from "reactstrap";
import update from "immutability-helper";

const TodoMain = ({index, todo, todoList}) => {
    const dispatch = useDispatch();
    // 갱신모드 설정
    const [isTitleUpdate, setIsTitleUpdate] = useState(false);
  
    // Todolist title 수정용 state
    const [changeTitle, setChangeTitle] = useState();
  
    // todolist 소분류 추가 입력 모드 활성화 state
    const [isContentsAdd, setIsContentsAdd] = useState(false);
    const [createInputContents, setCreateInputContents] = useState();
  
    // todolist 소분류 갱신모드 설정 여부
    const [isContentsUpdate, setIsContentsUpdate] = useState(false);
    // todolist 입력
    const [updateInputContents, setUpdateInputContents] = useState();
  
    /**
     * todolist 소분류 추가 작업 입력
     */
    const addContentsOnChange = (e, index) => {
      const { name, value } = e.target;
      setCreateInputContents({ [index]: { [name]: value } });
      console.log(createInputContents);
    };
    /**
     *  Todolist 소분류 추가하는 기능
     */
    const createContentsButton = (todo, index) => {
      let tmp = todo;
      tmp?.contents.push(createInputContents[index].contents);
      dispatch({
        type: "setTodoList",
        array: update(todoList, {
          $merge: {
            [index]: tmp,
          },
        }),
      });
    };
  
    // 수정용 메서드
    const updateTitlOnChange = (e, index) => {
      const { name, value } = e.target;
      setChangeTitle({ [index]: { [name]: value } });
      console.log(changeTitle);
    };
  
    // 수정 부분 입력 후 갱신 메서드
    const updateTitle = (index) => {
      /**
       * 불변성 유지하면서 갱신 시킬 수 있습니다.
       */
      dispatch({
        type: "setTodoList",
        array: update(todoList, {
          [index]: {
            title: { $set: changeTitle[index].title },
          },
        }),
      });
    };
  
    /**
     * 소분류용 갱신 메서드 입력
     */
    const updateContentsOnChange = (e, index, tcIndex) => {
      const { name, value } = e.target;
      setUpdateInputContents({ [index]: { [tcIndex]: { [name]: value } } });
      console.log(updateInputContents);
    };
  
    /**
     * 소분류용 갱신 메서드 수행 = state 값 반영
     */
    const updateContents = (index, tcIndex) => {
      /**
       * 불변성 유지하면서 갱신 시킬 수 있습니다.
       */
      let contents = updateInputContents[index][tcIndex].contents;
      let to = todoList[index];
      to.contents[tcIndex] = contents;
  
      dispatch({
        type: "setTodoList",
        array: update(todoList, {
          [index]: { $set: to },
        }),
      });
    };
  
    // 삭제 관련 메소드 (제목)
    const removeTitle = (index) => {
      /**
       * 불변성 유지하면서 삭제 시킬 수 있습니다.
       */
      dispatch({
        type: "setTodoList",
        array: update(todoList, {
          $splice: [[index, 1]],
        }),
      });
    };
  
    // 삭제 관련 메소드 (소분류)
    const removeContents = (index, tcIndex) => {
      let to = update(todoList[index].contents, {
        $splice: [[tcIndex, 1]],
      });
  
      console.log(to);
  
      dispatch({
        type: "setTodoList",
        array: update(todoList, {
            [index]: {
              contents: {$set: to},
            },
        }),
      });
  
      // console.log(tcIndex)
    };

  return (
    <div key={index} className="todoContainer">
      <div className="todoTitle">
        {isTitleUpdate ? (
          <div>
            <Input
              name="title"
              defaultValue={todo?.title}
              onChange={(e) => updateTitlOnChange(e, index)}
            />
            <Button onClick={() => updateTitle(index)}>todolist수정</Button>
            <Button onClick={() => setIsTitleUpdate(!isTitleUpdate)}>
              취소
            </Button>
          </div>
        ) : (
          <div>
            {todo?.title}
            <Button onClick={() => setIsTitleUpdate(!isTitleUpdate)}>
              수정
            </Button>{" "}
            <Button onClick={() => setIsContentsAdd(!isContentsAdd)}>
              <PlusCircle />
            </Button>
            <Button onClick={() => removeTitle(index)}>
              <Trash3 />
            </Button>
          </div>
        )}
      </div>

      {isContentsAdd && (
        <>
          {"소분류 추가용 Input : "}
          <Input
            name="contents"
            // defaultValue={}
            onChange={(e) => addContentsOnChange(e, index)}
          />
          <Button onClick={() => createContentsButton(todo, index)}>
            추가
          </Button>
        </>
      )}
      {todo?.contents?.map((tc, tcIndex) => (
        <div key={tcIndex} className="todoContents">
          {isContentsUpdate ? (
            <div>
              -
              <Input
                name="contents"
                defaultValue={tc}
                onChange={(e) => updateContentsOnChange(e, index, tcIndex)}
              />
              <Button onClick={() => updateContents(index, tcIndex)}>
                todolist수정
              </Button>
              <Button onClick={() => setIsContentsUpdate(!isContentsUpdate)}>
                취소
              </Button>
            </div>
          ) : (
            <div>
              - {tc}
              <Button onClick={() => setIsContentsUpdate(!isContentsUpdate)}>
                수정
              </Button>{" "}
              <Button onClick={() => removeContents(index, tcIndex)}>
                <Trash3 />
              </Button>
            </div>
          )}
        </div>
      ))}
    </div>
  );
};

export default TodoMain;

 

 

 

2. ./component/main/index.js 안에서 재분할 하기

아직도 코드가 길기 때문에 다시 재분할 합니다.

 

마찬가지로 대분류, 소분류로 나누고, 

분할 하는 김에 기존에 특정 TodoList 클릭시 전체 수정 모드로 전환 되는 원인이 발생하는 것을

해당 갱신 부분만 편집모드로 전환하여 적용시킵니다. 

 

대분류 ( ./component/category/main/big/index.js )

import React, { useState } from "react";
import { PlusCircle, Trash3 } from "react-bootstrap-icons";
import { useDispatch, useSelector } from "react-redux";
import { Button, Input } from "reactstrap";
import update from "immutability-helper";

const BigList = ({ todo, index, isContentsAdd, setIsContentsAdd }) => {
  const todoList = useSelector((state) => state.todoList.array);
  const dispatch = useDispatch();
  // 갱신모드 설정
  const [isTitleUpdate, setIsTitleUpdate] = useState(false);

  // Todolist title 수정용 state
  const [changeTitle, setChangeTitle] = useState();

  // 수정용 메서드
  const updateTitlOnChange = (e, index) => {
    const { name, value } = e.target;
    setChangeTitle({ [index]: { [name]: value } });
  };

  // 수정 부분 입력 후 갱신 메서드
  const updateTitle = (index) => {
    /**
     * 불변성 유지하면서 갱신 시킬 수 있습니다.
     */
    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        [index]: {
          title: { $set: changeTitle[index].title },
        },
      }),
    });
  };

  // 삭제 관련 메소드 (제목)
  const removeTitle = (index) => {
    /**
     * 불변성 유지하면서 삭제 시킬 수 있습니다.
     */
    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        $splice: [[index, 1]],
      }),
    });
  };
  return (
    <>
      <div className="todoTitle">
        {isTitleUpdate ? (
          <div>
            <Input
              name="title"
              defaultValue={todo?.title}
              onChange={(e) => updateTitlOnChange(e, index)}
            />
            <Button onClick={() => updateTitle(index)}>todolist수정</Button>
            <Button onClick={() => setIsTitleUpdate(!isTitleUpdate)}>
              취소
            </Button>
          </div>
        ) : (
          <div>
            {todo?.title}
            <Button onClick={() => setIsTitleUpdate(!isTitleUpdate)}>
              수정
            </Button>{" "}
            <Button onClick={() => setIsContentsAdd(!isContentsAdd)}>
              <PlusCircle />
            </Button>
            <Button onClick={() => removeTitle(index)}>
              <Trash3 />
            </Button>
          </div>
        )}
      </div>
    </>
  );
};

export default BigList;

 

 

 

 

소분류 ( ./component/caregory/main/small/index.js )

  {isContentsUpdate ? (
    <div>
      -
      <Input
        name="contents"
        defaultValue={tc}
        onChange={(e) => updateContentsOnChange(e, index, tcIndex)}
      />
      <Button onClick={() => updateContents(index, tcIndex)}>
        todolist수정
      </Button>
      <Button onClick={() => setIsContentsUpdate(!isContentsUpdate)}>
        취소
      </Button>
    </div>
  ) : (
    <div>
      - {tc}
      <Button onClick={() => setIsContentsUpdate(!isContentsUpdate)}>
        수정
      </Button>{" "}
      <Button onClick={() => removeContents(index, tcIndex)}>
        <Trash3 />
      </Button>
    </div>
  )}

 

TodoList main ( ./component/main/index.js ) 

import React, { useEffect, useState } from "react";

import BigList from "./category/big/index";
import SmallList from "./category/small/index";
import { useDispatch, useSelector } from "react-redux";
import update from "immutability-helper";
import { Button, Input } from "reactstrap";

const TodoMain = ({ index, todo, isContentsAdd, setIsContentsAdd }) => {
  const todoList = useSelector((state) => state.todoList.array);
  const dispatch = useDispatch();

  const [createInputContents, setCreateInputContents] = useState();

  /**
   * todolist 소분류 추가 작업 입력
   */
  const addContentsOnChange = (e, index) => {
    const { name, value } = e.target;
    setCreateInputContents({ [index]: { [name]: value } });
    console.log(createInputContents);
  };
  /**
   *  Todolist 소분류 추가하는 기능
   */
  const createContentsButton = (todo, index) => {
    let tmp = todo;
    tmp?.contents.push(createInputContents[index].contents);
    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        $merge: {
          [index]: tmp,
        },
      }),
    });
  };

  useEffect(() => {}, [todoList]);
  return (
    <div key={index} className="todoContainer">
      <BigList
        todo={todo}
        todoList={todoList}
        index={index}
        isContentsAdd={isContentsAdd}
        setIsContentsAdd={setIsContentsAdd}
      />

      {isContentsAdd && (
        <>
          {"소분류 추가용 Input : "}
          <Input
            name="contents"
            onChange={(e) => addContentsOnChange(e, index)}
          />
          <Button onClick={() => createContentsButton(todo, index)}>
            추가
          </Button>
        </>
      )}

      {todo?.contents?.map((tc, tcIndex) => (
        <div key={tcIndex} className="todoContents">
          <SmallList
            tc={tc}
            index={index}
            tcIndex={tcIndex}
            todoList={todoList}
          />
        </div>
      ))}
    </div>
  );
};

export default TodoMain;

 

여기까지 했으면 비교적 깔끔하다.

하지만, 추가 삭제시 바로 적용할 수 있게 코드 일부 수정해보자.

 

 

3. 대분류 갱신, 소분류 추가 버튼, 갱신 버튼 누르자마자 바로 데이터 추가 되게 적용하기

- 소분류 추가시 입력모드 취소

- 소분류 갱신 후 바로 입력 모드 해제

- 대분류 갱신 후 바로 입력 모드 해제

 

이후 정리 하면 다음과 같다.

 

< ./component/main/index.js >

import React, { useState } from "react";

import BigList from "./category/big/index";
import SmallList from "./category/small/index";
import { useDispatch, useSelector } from "react-redux";
import update from "immutability-helper";
import { Button, Input } from "reactstrap";

const TodoMain = ({ index, todo }) => {
  const todoList = useSelector((state) => state.todoList.array);
  const dispatch = useDispatch();

    // todolist 소분류 추가 입력 모드 활성화 state
  // 여기는 소분류 입력 모드 활성화 및 비활성화 작업을 진행함
  const [isContentsAdd, setIsContentsAdd] = useState(false);

  // 다음은 생성시 바로 state문에 이벤트 표시되도록 설정
  const [isBehaviorChange, setIsBehaviorChange] = useState(false);

  const [createInputContents, setCreateInputContents] = useState();

  /**
   * todolist 소분류 추가 작업 입력
   */
  const addContentsOnChange = (e, index) => {
    const { name, value } = e.target;
    setCreateInputContents({ [index]: { [name]: value } });
    console.log(createInputContents);
  };
  /**
   *  Todolist 소분류 추가하는 기능
   */
  const createContentsButton = (todo, index) => {
    let tmp = todo;
    tmp?.contents.push(createInputContents[index].contents);
    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        $merge: {
          [index]: tmp,
        },
      }),
    });
    // 생성 후 바로 입력모드 취소
    setIsContentsAdd(!isContentsAdd)
  };

  return (
    <div key={index} className="todoContainer">
      <BigList
        todo={todo}
        todoList={todoList}
        index={index}
        isContentsAdd={isContentsAdd}
        setIsContentsAdd={setIsContentsAdd}
      />

      {isContentsAdd && (
        <>
          {"소분류 추가용 Input : "}
          <Input
            name="contents"
            onChange={(e) => addContentsOnChange(e, index)}
          />
          <Button onClick={() => createContentsButton(todo, index)}>
            추가
          </Button>
        </>
      )}

      {todo?.contents?.map((tc, tcIndex) => (
        <div key={tcIndex} className="todoContents">
          <SmallList
            tc={tc}
            index={index}
            tcIndex={tcIndex}
            todoList={todoList}
            isBehaviorChange={isBehaviorChange} setIsBehaviorChange={setIsBehaviorChange}
          />
        </div>
      ))}
    </div>
  );
};

export default TodoMain;

 

 

 

< ./component/main/category/big/index.js >

 

import React, { useState } from "react";
import { PlusCircle, Trash3 } from "react-bootstrap-icons";
import { useDispatch, useSelector } from "react-redux";
import { Button, Input } from "reactstrap";
import update from "immutability-helper";

const BigList = ({ todo, index, isContentsAdd, setIsContentsAdd}) => {
  const todoList = useSelector((state) => state.todoList.array);
  const dispatch = useDispatch();
  
  // 갱신모드 설정
  const [isTitleUpdate, setIsTitleUpdate] = useState(false);

  // Todolist title 수정용 state
  const [changeTitle, setChangeTitle] = useState();

  // 수정용 메서드
  const updateTitlOnChange = (e, index) => {
    const { name, value } = e.target;
    setChangeTitle({ [index]: { [name]: value } });
  };

  // 수정 부분 입력 후 갱신 메서드
  const updateTitle = (index) => {
    /**
     * 불변성 유지하면서 갱신 시킬 수 있습니다.
     */
    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        [index]: {
          title: { $set: changeTitle[index].title },
        },
      }),
    });
    // 갱신 후 바로 입력모드 해제
    setIsTitleUpdate(!isTitleUpdate)
    
  };

  // 삭제 관련 메소드 (제목)
  const removeTitle = (index) => {
    /**
     * 불변성 유지하면서 삭제 시킬 수 있습니다.
     */
    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        $splice: [[index, 1]],
      }),
    });
  };
  return (
    <>
      <div className="todoTitle">
        {isTitleUpdate ? (
          <div>
            <Input
              name="title"
              defaultValue={todo?.title}
              onChange={(e) => updateTitlOnChange(e, index)}
            />
            <Button onClick={() => updateTitle(index)}>todolist수정</Button>
            <Button onClick={() => setIsTitleUpdate(!isTitleUpdate)}>
              취소
            </Button>
          </div>
        ) : (
          <div>
            {todo?.title}
            <Button onClick={() => setIsTitleUpdate(!isTitleUpdate)}>
              수정
            </Button>{" "}
            <Button onClick={() => setIsContentsAdd(!isContentsAdd)}>
              <PlusCircle />
            </Button>
            <Button onClick={() => removeTitle(index)}>
              <Trash3 />
            </Button>
          </div>
        )}
      </div>
    </>
  );
};

export default BigList;

 

 

 

< ./component/main/category/small/index.js >

import React, { useState } from "react";
import { Trash3 } from "react-bootstrap-icons";
import { Button, Input } from "reactstrap";
import update from "immutability-helper";
import { useDispatch, useSelector } from "react-redux";

const SmallList = ({
  tc,
  index,
  tcIndex,
  isBehaviorChange, setIsBehaviorChange
}) => {
  const dispatch = useDispatch();
  const todoList = useSelector((state) => state.todoList.array);
  // todolist 입력
  const [updateInputContents, setUpdateInputContents] = useState();

    // todolist 소분류 갱신모드 설정 여부
    const [isContentsUpdate, setIsContentsUpdate] = useState(false);

  /**
   * 소분류용 갱신 메서드 입력
   */
  const updateContentsOnChange = (e, index, tcIndex) => {
    const { name, value } = e.target;
    setUpdateInputContents({ [index]: { [tcIndex]: { [name]: value } } });
    console.log(updateInputContents);
  };

  /**
   * 소분류용 갱신 메서드 수행 = state 값 반영
   */
  const updateContents = (index, tcIndex) => {
    /**
     * 불변성 유지하면서 갱신 시킬 수 있습니다.
     */
    let contents = updateInputContents[index][tcIndex].contents;
    let to = todoList[index];
    to.contents[tcIndex] = contents;

    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        [index]: { $set: to },
      }),
    });
    // 갱신후 수정모드 취소
    setIsContentsUpdate(!isContentsUpdate);
    // 갱신 후 바로 state 문 적용을 위해 사용
    setIsBehaviorChange(!isBehaviorChange);
  };

  // 삭제 관련 메소드 (소분류)
  const removeContents = (index, tcIndex) => {
    let to = update(todoList[index].contents, {
      $splice: [[tcIndex, 1]],
    });

    dispatch({
      type: "setTodoList",
      array: update(todoList, {
        [index]: {
          contents: { $set: to },
        },
      }),
    });

    // console.log(tcIndex)
  };
  return (
    <>
      {isContentsUpdate ? (
        <div>
          -
          <Input
            name="contents"
            defaultValue={tc}
            onChange={(e) => updateContentsOnChange(e, index, tcIndex)}
          />
          <Button onClick={() => updateContents(index, tcIndex)}>
            todolist수정
          </Button>
          <Button onClick={() => setIsContentsUpdate(!isContentsUpdate)}>
            취소
          </Button>
        </div>
      ) : (
        <div>
          - {tc}
          <Button onClick={() => setIsContentsUpdate(!isContentsUpdate)}>
            수정
          </Button>{" "}
          <Button onClick={() => removeContents(index, tcIndex)}>
            <Trash3 />
          </Button>
        </div>
      )}
    </>
  );
};

export default SmallList;

 

 

(4-2) 분할 대상 2

다음은 추가하는 부분입니다.

해당 컴포넌트 사용시, 다음과 같이 분할 할 수 있습니다.

 

이 코드와 관련 있는 기능 및 render 관련된 부분들은 ./component/create/index.js 파일로 이동하여 모듈화 시킵니다.

이후 App.js 에서 아래와 같이 코드 추가 후 해당 모듈을 호출합니다.

import CreateTitle from "./component/create/index"

 

다음은 App.js에서 해당하는 렌더링 코드 (Form 으로 구성된 코드) 와 입력시 사용해야 하는 메소드

creteTitleOnchange, createTitleButton 를 옮겨주고, useDispatch로 통해 state 값을 불려옵니다.

 

이후 render 코드에서는 "CreateTitle"만 추가해주면 똑같이 적용됩니다.

retuen(
	<div>
    	{/* ..... */}
    	<CreateTitle /> {/* 이부분은 대분류 생성 기능 동일하게 가능*/}
    </div>
)

 

 

< ./component/create/index.js 결과 >

import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux';
import { Button, Form, Input } from 'reactstrap';

const TodoCreate = () => {
  const todoList = useSelector((state) => state.todoList.array);
  const dispatch = useDispatch();
  const [createInputTitle, setCreateInputTitle] = useState();
   /**
   * 다음은 TodoList 입력용 이벤트 함수
   */
   const createTitleOnchange = (e) => {
    const { name, value } = e.target;
    setCreateInputTitle({
      ...createInputTitle,
      [name]: value,
    });
  };
  /**
   * 입력시 Todolist 추가하는 기능
   */
  const createTitleButton = () => {
    let to = [];
    to.push(...todoList);
    to.push({
      title: createInputTitle?.title,
      contents: [],
    });
    /**
     * dispatch는 todolist.js 의 reducer의 action으로 사용되는 함수로
     * state 값을 변경시켜준다.
     * redux의 state 값은 불변성을 유지하는 것이 목적이다.
     * switch문의 action.type 변수에 따라 return으로 명령어대로 해당 state 값을 변경 시켜준다.
     */
    dispatch({ type: "setTodoList", array: to });
  };
  return ( 
    <Form className="addGroup">
    <Input
      className="addInput"
      name="title"
      defaultValue={createInputTitle}
      onChange={createTitleOnchange}
    />
    <Button className="addButton" onClick={createTitleButton}>
      추가
    </Button>
  </Form>
  )
}

export default TodoCreate;

 

 

마치며,

아직까지는 저역시도 클린코드의 대해 부족한 점이 많지만,

할수 있는 한 최대한 적용을 시켜보았습니다.

이렇게 코드수를 줄이고, 효과적으로 코드를 작성함과 동시에 이벤트 클릭시 각각 따로 지정할 수 있는 기능들과

사용자 편의성까지 일부 변경하였습니다.

 

 

마지막으로 응용하여 드래그앤 드롭을 만들겠습니다.

https://berkley.tistory.com/15

 

(10) TodoList 드래그앤드롭 적용하기

지난 시간에는 클린 코딩을 작성하는 것으로 마치였습니다. 지금 부터 드래그앤 드롭으로 TodoList의 순서를 변경하도록 적용하겠습니다. 이전시간에 이어서 진행하도록 하겠습니다. (9) Todo List

berkley.tistory.com

 

728x90
반응형
LIST