본문 바로가기
항해99/온보딩 스터디

[항해99] 온보딩 스터디 - 2주차 4일 TIL

by junvely 2023. 3. 17.

🦄 온보딩 스터디 2주차 4일 TIL

✨03/16 : 목표 : Todo List 만들기

온보딩 스터디 알고리즘 4일차, 엑셀로 주신 프로그래머스 문제는 전부 풀었기 때문에 기술 매니저님께서 과제로 주신 Todo List 를 만들어 보기로 했다.


📒Todo List 만들어 보기

01 기능 구현 목표

 
TodoApp은 할일 목록을 기록하고 관리하기 위한 어플리케이션이다.

[요구사항]
할일 리스트 정보를 갖는다.
할일을 추가할 수 있다.
할일 항목(item)은 제목, 설명, 완료여부를 포함하며 수정 또는 삭제가 가능하다.
완료 여부의 경우 완료 상태를 취소 할 수 있다.

[구현]
할일 항목을 다루기 위한 정보를 데이터화 합니다.
할일 항목들을 리스트로 관리합니다.
각 항목들을 제어하기 위한 로직을 작성 합니다.

 

구현 단계

1. 투두 리스트 html, css 정적 화면 생성

2. 투두 리스트 배열로 관리하기

3. 투두 리스트 배열 안의 객체 가져오기, 추가하기, 삭제하기, 수정하기 기능 => 비파괴적 방식으로

3. 투두 리스트 배열을 html요소로 화면에 나타내기

 

 

02 기능 구현 

1. 투두 리스트 배열로 데이터 관리하기

let todos = [
  {
    id: getRandomKey(),
    todo: "아침먹기",
    done: false,
  },
];

 

2. 투두 리스트 배열에 추가, 삭제, 수정 하기

//객체 추가
const addTodo = () => {
  const newTodo = {
    id: getRandomKey(), //고유 키 생성
    todo: todoInput.value,
    done: false,
  };

  const updateTodo = [...todos, newTodo];
  todos = updateTodo;
  renderTodos();
};

//객체 삭제
const deleteTodo = (event) => {
  const target = event.target.parentElement;

  const updateTodo = todos.filter((todo) => {
    return todo.id !== Number(target.dataset.id); // todo.id는 number => target.id는 string => 숫자로 타입 변환
  });

  todos = updateTodo;
  renderTodos();
};

//객체 수정 => done 여부
const modifyTodo = (event) => {
  const target = event.target.parentElement;
  const updateTodo = todos.map((todo) => {
    if (todo.id === Number(target.dataset.id)) {
      if (todo.done) {
        return {
          ...todo,
          done: false,
        };
      } else {
        return {
          ...todo,
          done: true,
        };
      }
    }
    return { ...todo };
  });
  todos = updateTodo;
  renderTodos();
};

- 고유의 키 생성하기 => id는 고유의 값이어야 하기 때문에 random한 고유의 키를 생성해야 한다. random 값을 이용하거나, math.random이나 getTime 등은 => 동시에 여러 자료를 출력 시, 중복의 위험이 있기 때문에 getTime*math.random 해 주었다. UUID를 사용하는 것도 좋을 것 같다.

const getRandomKey = () => {
  return date.getTime() * Math.random();
};

 

3. 이벤트 발생시 함수 실행시키기

- Submit시 마다 addTodo함수 실행

todoSubmit.addEventListener("click", addTodo);

- delete와 done버튼 클릭 시 마다 각각의 함수 실행

ul.addEventListener("click", handleClickEvent);

♻️ 리팩토링)

❗이벤트 위임 : ul 내부의 모든 자식요소들을 클릭하면 event정보를 전달해 준다.

handleClickEvent함수를 실행시 event.target의 정보를 비교한 후,

일치할 시 해당 함수를 실행하도록 설정해준다. => dataset을 html에 설정해 주어 해당 속성에 접근해 비교 해주었다.

const handleClickEvent = (event) => {
  const target = event.target;
  if (target.dataset.delete) {
    deleteTodo(event);
  } else if (target.dataset.done) {
    modifyTodo(event);
  }
};

 => event.target의 class나 id, dataset 속성으로 접근 가능하다.

나는 li를 삭제나 수정하고 싶기 때문에, 버튼의 부모 요소를 target으로 잡고 dataset 속성으로 접근했다.

const deleteTodo = (event) => {
  const target = event.target.parentElement;

  const updateTodo = todos.filter((todo) => {
    return todo.id !== Number(target.dataset.id); // todo.id는 number => target.id는 string => 숫자로 타입 변환
  });

  todos = updateTodo;
  renderTodos();
};

 

4. 객체가 업데이트 될 때마다, 업데이트된 투두리스트를 html요소로 render시키기

실패 1 )원래 아래와 같이 코드를 작성했는데, 이렇게 되면 innerHTML이 실행될 때 마다, ul안의 요소들을 모두 초기화하며 가져오기 때문에 for문을 돌 때마다, 또는 함수 실행 시 마다 새로운 투두로 초기화 된다.

const renderTodos = () => {
    todos.forEach((todo) => {
        ul.innerHTML = `<li class="todo" data-id=${todo.id}>
                          <input type="checkbox" class="check" checked>
                          <input type="checkbox" class="check">
                          <p class="todoText">${todo.todo}</p>
                          <button class="delete" data-delete="delete">Delete</button>
                          <button class="done" data-done="done">Done</button>
                        </li>
                `;
      });
}

실패 2 ) 아래와 같이 일일히 추가해 줬더니, 객체가 추가, 삭제, 수정 될 때마다 render함수가 실행되어 요소가 끝도없이 추가된다..

const renderTodos = () => {
    todos.forEach((todo) => {
        const li = document.createElement("li");
        li.setAttribute("class", "todo");
        li.setAttribute("id", getRandomKey());
        ul.appendChild(li);

        const checkbox = document.createElement("input");
        checkbox.setAttribute("type", "checkbox");
        checkbox.setAttribute("class", "check");
        checkbox.checked = todo.done;
        li.appendChild(checkbox);

        const p = document.createElement("p");
        p.setAttribute("class", "todoText");
        p.innerText = todo.todo;
        li.appendChild(p);

        const deleteBtn = document.createElement("button");
        deleteBtn.setAttribute("class", "delete");
        deleteBtn.setAttribute("data-delete", "delete");
        deleteBtn.innerText = "Delete";
        li.appendChild(deleteBtn);

        const doneBtn = document.createElement("button");
        doneBtn.setAttribute("class", "done");
        deleteBtn.setAttribute("data-done", "done");
        doneBtn.innerText = "Done";
        li.appendChild(doneBtn);
      });
 }

♻️ 리팩토링)

성공 ) html문자열을 따로 변수로 놓고, +=해서 for문을 돌 때 마다 객체를 HTML요소들로 문자열로 추가해 준 후, 다 생성된 HTML요소들을 ul.innerHTML해 주었다.

const renderTodos = () => {
  let html = ``;
  todos.forEach((todo) => {
    html += `<li class="todo" data-id=${todo.id}>`;
    // 객체의 done여부에 따라 체크여부가 결정되기 때문에 if문으로 분리해주었다. 
    if (todo.done) {
      html += `<input type="checkbox" class="check" checked>`;
    } else {
      html += `<input type="checkbox" class="check">`; 
    }
    html += `
              <p class="todoText">${todo.todo}</p>
              <button class="delete" data-delete="delete">Delete</button>
              <button class="done" data-done="done">Done</button>
            </li>
            `;
  });

  ul.innerHTML = html;
};

 

=> 객체의 done여부에 따라 체크여부가 결정되기 때문에 if문으로 분리해주었다. 스타일을 달리 설정할 때에도 분리해 주어야할 것 같다.

 

🙌완성 화면)

아직 미완성이기 때문에 여러가지 기능들도 추가해야 겠다!

 

✅배운 점

바닐라 자바스크립트 프로젝트를 강의의 도움 없이 혼자 처음부터 만들어 본 것은 오랜만인 것 같다.

기술 매니저님의 도움을 받아 더 나은 코드로 리팩토링 해 보면서 느낀 부분들을 정리해 보았다.

1. Event 위임 => 부모 요소에 이벤트를 걸면, 자식 요소들에게도 이벤트가 전달되어 자식 요소를 클릭해도 해당 요소의 event 정보를 얻을 수 있다. 이벤트를 걸어야 할 요소들이 많을 때, 일일이 이벤트를 거는 번거로움 없이 부모 요소에게 한 번만 이벤트를 걸어주면 자식 요소에게도 이벤트가 위임된다.

2. event.target으로 class나 id, dataset 속성에 접근이 가능하다. 따라서 어떤 함수에게 매개변수로 어떤 정보를 따로 전달해 주지 않아도, 클릭 시 event.target으로 바로 정보를 받아올 수 있다.

3. html의 dataset 속성을 사용하면 사용자가 설정한 data-name으로 특정 데이터값을 줄 수 있고, event.target에서 접근이 가능하다.

4. 로직 분리 => 처음에는 하나의 함수에서 객체를 수정하고, render까지 하도록 하였다. ex) addTodo+render따로, deleteTodo+render 따로... => 객체를 추가,삭제,수정 등 새로운 데이터로 업데이트 하는 함수를 따로 두고, 업데이트 시 마다 rendering 함수에서 업데이트된 todos객체만 참조하여 rendering하도록 따로 함수를 두면, 업데이트 시 마다 addTodo 후 render()를 실행하도록 하면 된다.

이렇게 하면 매개변수로 정보를 계속 주입하던 함수의 의존성도 끊어지고,  render함수는 오직 업데이트가 완료된 todos의 객체만 참조하여 렌더링 시키기 때문에 코드 관리에도 용이하다. 

굉장히 중요한 개념들을 todo list에서 배운 것 같다. 이제 투두리스트 완료 여부에 따라 스타일을 변경 한다던지 좀 더 기능을 더 추가하고, DB와 연동도 해서 완성된 하나의 웹앱을 만들어 봐야겠다!