ksundev 님의 블로그

[TODO App] 동적 앱 만들기 (TodoList.vue) 본문

[개발] Vue.js/중급

[TODO App] 동적 앱 만들기 (TodoList.vue)

ksundev 2025. 7. 6. 16:03

Vue.js 동적 앱 만들기 - TodoList 컴포넌트 구현

저번 포스트인 TodoInput.vue에 이어서 이번 포스트에서는 Todo(할일)를 나타낼 리스트를 완성해보겠습니다.

1. data()에 할 일 목록을 저장할 배열 정의하기

TodoList 컴포넌트에서는 여러 개의 할 일을 관리해야 하므로 배열을 사용합니다.

export default {
  data() {
    return {
      todoItems: [], // 할 일 목록을 저장할 배열
    };
  },
  // ... 나머지 코드
};

todoItems 배열의 역할

  • 할 일 목록 저장: 여러 개의 Todo 객체를 배열 형태로 관리합니다
  • 동적 렌더링: 배열의 각 요소를 화면에 동적으로 표시합니다
  • 상태 관리: 할 일의 추가, 삭제, 완료 상태 변경을 추적합니다

2. created() 생명주기 훅으로 초기 데이터 로드하기

컴포넌트가 생성될 때 localStorage에서 기존 데이터를 불러와야 합니다.
참고로 loglevel:webpack-dev-server는 자동생성되는 관련없는 항목이므로 제외해줍니다.

created() {
  if (localStorage.length > 0) {
    for (var i = 0; i < localStorage.length; i++) {
      if (localStorage.key(i) !== "loglevel:webpack-dev-server") {
        this.todoItems.push(
          JSON.parse(localStorage.getItem(localStorage.key(i)))
        );
      }
    }
  }
},

created() 훅의 역할

  • 컴포넌트 생성 시점: DOM이 생성되기 전에 실행되어 초기 데이터를 준비합니다
  • localStorage 접근: 브라우저 저장소에서 기존 할 일 데이터를 가져옵니다
  • 데이터 변환: JSON 문자열을 JavaScript 객체로 변환하여 배열에 추가합니다

localStorage 데이터 로드 과정

  1. 저장소 확인: localStorage.length > 0으로 데이터가 있는지 확인
  2. 반복 접근: for 루프로 localStorage의 모든 키에 접근
  3. 필터링: webpack 개발 서버 관련 키는 제외
  4. 데이터 파싱: JSON.parse()로 문자열을 객체로 변환
  5. 배열 추가: push() 메서드로 todoItems 배열에 추가

3. v-for 디렉티브로 할 일 목록 렌더링하기

배열의 각 요소를 화면에 동적으로 표시하기 위해 v-for를 사용합니다.

<template>
  <div>
    <ul>
      <li v-for="(todoItem, index) in todoItems" :key="index" class="shadow">
        <i
          class="checkBtn fa-solid fa-check"
          :class="{ checkBtnCompleted: todoItem.completed }"
          @click="toggleComplete(todoItem, index)"
        ></i>
        <span :class="{ textCompleted: todoItem.completed }">{{
          todoItem.item
        }}</span>
        <span class="removeBtn" @click="removeTodo(todoItem, index)">
          <i class="fa-solid fa-trash-can"></i>
        </span>
      </li>
    </ul>
  </div>
</template>

v-for 디렉티브 분석

v-for="(todoItem, index) in todoItems" :key="index"
  • 반복 렌더링: todoItems 배열의 각 요소에 대해 <li> 요소를 생성합니다
  • 매개변수: todoItem은 현재 할 일 객체, index는 배열의 인덱스입니다
  • key 속성: Vue가 각 요소를 고유하게 식별할 수 있도록 인덱스를 키로 사용합니다

동적 클래스 바인딩

:class="{ checkBtnCompleted: todoItem.completed }"
:class="{ textCompleted: todoItem.completed }"
  • 조건부 스타일링: todoItem.completed 값에 따라 클래스를 동적으로 적용합니다
  • 완료 상태 표시: 할 일이 완료되면 시각적으로 다른 스타일을 적용합니다

4. methods에 할 일 관리 기능 구현하기

할 일의 완료 상태 변경과 삭제 기능을 구현합니다.

methods: {
  removeTodo(todoItem, index) {
    localStorage.removeItem(todoItem.item);
    this.todoItems.splice(index, 1);
  },
  toggleComplete(todoItem, index) {
    todoItem.completed = !todoItem.completed;
  },
},

removeTodo() 메서드 분석

removeTodo(todoItem, index) {
  localStorage.removeItem(todoItem.item);
  this.todoItems.splice(index, 1);
}

1) localStorage에서 삭제

  • 키 기반 삭제: todoItem.item을 키로 사용하여 localStorage에서 해당 항목을 삭제합니다
  • 영구 제거: 브라우저를 새로고침해도 삭제된 항목은 복구되지 않습니다

2) 배열에서 제거

  • splice() 메서드: index 위치에서 1개의 요소를 제거합니다
  • UI 자동 업데이트: 배열이 변경되면 Vue가 자동으로 화면을 다시 렌더링합니다

toggleComplete() 메서드 분석

toggleComplete(todoItem, index) {
  todoItem.completed = !todoItem.completed;
}

1) 상태 토글

  • 논리 부정: !todoItem.completed로 현재 상태의 반대값으로 변경합니다
  • 반응형 업데이트: 객체 속성이 변경되면 Vue가 자동으로 관련 UI를 업데이트합니다

2) 시각적 피드백

  • 동적 클래스: completed 값에 따라 체크 아이콘과 텍스트 스타일이 변경됩니다
  • 사용자 경험: 완료된 할 일은 시각적으로 구분되어 표시됩니다

5. 이벤트 핸들링과 매개변수 전달

템플릿에서 메서드를 호출할 때 필요한 데이터를 전달합니다.

@click="toggleComplete(todoItem, index)"
@click="removeTodo(todoItem, index)"

매개변수 전달의 중요성

  • todoItem: 현재 처리할 할 일 객체 (완료 상태, 내용 등 포함)
  • index: 배열에서의 위치 (삭제 시 정확한 위치 지정)
  • 고유 식별: 각 할 일을 개별적으로 처리할 수 있게 합니다

6. 전체 데이터 흐름 이해하기

  1. 초기 로드: created() 훅에서 localStorage의 모든 할 일을 todoItems 배열에 로드
  2. 화면 렌더링: v-for로 배열의 각 요소를 <li> 요소로 변환하여 표시
  3. 완료 토글: 체크 아이콘 클릭 시 toggleComplete()completed 상태 변경
  4. 항목 삭제: 휴지통 아이콘 클릭 시 removeTodo()로 localStorage와 배열에서 제거
  5. 자동 업데이트: 데이터 변경 시 Vue의 반응형 시스템이 자동으로 UI 업데이트

7. 완성된 TodoList.vue 컴포넌트

<template>
  <div>
    <ul>
      <li v-for="(todoItem, index) in todoItems" :key="index" class="shadow">
        <i
          class="checkBtn fa-solid fa-check"
          :class="{ checkBtnCompleted: todoItem.completed }"
          @click="toggleComplete(todoItem, index)"
        ></i>
        <span :class="{ textCompleted: todoItem.completed }">{{
          todoItem.item
        }}</span>
        <span class="removeBtn" @click="removeTodo(todoItem, index)">
          <i class="fa-solid fa-trash-can"></i>
        </span>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      todoItems: [],
    };
  },
  created() {
    if (localStorage.length > 0) {
      for (var i = 0; i < localStorage.length; i++) {
        if (localStorage.key(i) !== "loglevel:webpack-dev-server") {
          this.todoItems.push(
            JSON.parse(localStorage.getItem(localStorage.key(i)))
          );
        }
      }
    }
  },
  methods: {
    removeTodo(todoItem, index) {
      localStorage.removeItem(todoItem.item);
      this.todoItems.splice(index, 1);
    },
    toggleComplete(todoItem, index) {
      todoItem.completed = !todoItem.completed;
    },
  },
};
</script>

이제 TodoList 컴포넌트가 완성되었습니다! 저장된 할 일들을 화면에 표시하고, 완료 상태를 토글하며, 필요 없는 항목을 삭제할 수 있는 완전히 동작하는 할 일 목록이 되었습니다.

다음 포스트에서는 Todo 목록을 전체 삭제하는 TodoFooter 컴포넌트를 구현하여 완전한 Todo 앱을 완성해보겠습니다!