제목의 글을 설명하기에 앞서 정보전달의 목적도 있지만,
제가 잊지않기 위해서 공부 및 정리하며 쓰는 글이라는 사실을 미리 고지합니다.
혹시라도 오입력된 정보가 있다면, 댓글 남겨주세요!
오늘의 Vue.js 실습 목표는 "투두 리스트 (Todo list) 만들어보기!" 입니다.
본 게시글에서 다루는 사항은 투두리스트(Todo List)의 최상위 컴포넌트 기능을 구현하는 과정입니다.
투두 리스트(Todo List) 구현하기
사전 준비
아래 두가지에 대해서 사전 준비가 완료되지 않으신 분들은 아래 링크를 참조하여 사전 준비를 진행합니다.
- 투두 리스트 레이아웃(Todo List Layout) 구현하기: https://logs-jejustone.tistory.com/37
혹시라도 위에서 언급한 개념이 잘 기억나지 않으시는 분들은 아래 링크를 참조하시기 바랍니다.
- 뷰 인스턴스 상태 (Vue Instance State): https://logs-jejustone.tistory.com/27
- 뷰 이벤트 (Vue Event): https://logs-jejustone.tistory.com/26
- 뷰 데이터 바인딩 (Vue Data Binding): https://logs-jejustone.tistory.com/22
- WEB Local Storage vs Session Storage: https://logs-jejustone.tistory.com/56
결과 이미지
구현하기에 앞서 본 글을 따라하시면 아래와 같이 투두 리스트의 결과를 콘텐츠 영역에서 확인하실 수 있습니다.
기본
UI

DB / Console

Todo Item 추가(Add) 기능
UI



DB / Console


Todo Item 삭제(Delete) 기능
UI


DB / Console


Todo Item 완료 처리(Done, Checked) 기능
UI


Todo Item 전체 삭제(Delete All) 기능
UI


DB / Console


주의 사항
Vue 실습 프로젝트로 Todo List가 정석처럼 여겨지고 있어서 구글링해보면 정말 많은 게시글이 확인되지만 대체로 비슷비슷한 소스코드로 구현되어 있습니다.
TodoList를 구현할 때 Local Storage를 DB로 사용하는데 Local Storage에 데이터를 저장하고 있으나 실질적으로 화면에 표시하는 데이터로는 사용하지 않습니다.
따라서 최상위 컴포넌트에서 화면에 표시할 데이터를 Local Storage와 동일한 형식을 갖추도록 구현해야 합니다.
잘 못 된 코드
1) Local Storage은 {key, value} 구조로 입력받은 텍스트를 key와 value에 동일하게 입력하는 경우
동일한 데이터를 N번 입력하는 경우 화면에는 N번 표시되나 Local Storage에는 1번만 저장됩니다.
따라서 이 현상으로 인해 Local Storage에는 Key 중복으로 인해 데이터가 정상적으로 저장되지 않는 문제가 발생합니다.
let newTodoText = "사용자가 입력한 텍스트";
localStorage.setItem(newTodoText, newTodoText);
2) 화면에 표시하기 위해 저장하는 변수인 todoItems를 단순 배열(Array)로만 지정하는 경우
Todo List에서 개별 아이템 삭제시 todoItems 내 화면에 동일하게 표시되는 데이터가 있는 경우 todoItems은 선택된 Todo Item이 아닌 중복된 데이터 중 하나가 삭제될 수 있습니다.
즉, 사용자가 선택한 데이터가 아닌 데이터가 삭제되고 Local Storage와 todoItems가 상이한 데이터를 지니는 문제가 발생합니다.
data: function() {
return {
todoItems: []
}
}
methods: {
pushItem: function(newTodoText) {
this.todoItems.push(newTodoText);
}
}
해결 방법 (정상 동작 코드)
1) Local Storage은 {key, value} 구조로 입력받은 텍스트를 key와 value에 동일하게 입력하는 경우
Local Storage의 {key, value} 구조에 맞게 key는 unique id/value를 지정하고, value에는 사용자에게 입력받은 텍스트를 사용하여 Local Storage에서 발생하는 중복 Key 문제를 해결할 수 있습니다.
let todoId = Date.now(); // for unique id/value
let newTodoText = "사용자가 입력한 텍스트";
localStorage.setItem(todoId, newTodoText);
2) 화면에 표시하기 위해 저장하는 변수인 todoItems를 단순 배열(Array)로만 지정하는 경우
Local Storage와 동일한 구조를 지닌 배열(Object를 지닌 Array)을 사용하여 각 Todo Item에 대한 key와 value, 그리고 화면 요소에서 표시되는 인덱스를 통해 상이한 데이터가 관리되거나 선택되지 않은 데이터가 삭제되는 문제를 해결할 수 있습니다.
data: function() {
return {
todoItems: []
}
}
methods: {
pushItem: function(newTodoText) {
let todoItem = {
key: todoId,
value: newTodoText
};
this.todoItems.push(todoItem);
}
}
> 화면 데이터 (todoItems)
Array:index | 0 | 1 | ... |
Array:data └ Object:key |
1671437696232 | 1671437702518 | ... |
Array:data └ Object:value |
투두 리스트 기능 구현하기 | 투두 리스트 CSS 적용하기 | ... |
> Local Storage
Object:key | 1671437696232 | 1671437702518 | ... |
Object:value | 투두 리스트 기능 구현하기 | 투두 리스트 CSS 적용하기 | ... |
소스 코드
src\views\Practice\PracticeTodo.vue
Todo List의 최상위 컴포넌트로 하위 컴포넌트에서 전달받은 이벤트 시그널과 최상위 컴포넌트에서 관리하는 자체 데이터를 구현합니다.
<template>
<template>
<div id="todo">
<TodoHeader></TodoHeader>
<TodoInput v-on:addItem="addItem"></TodoInput>
<TodoList v-bind:propsdata="todoItems" v-on:removeItem="removeItem"></TodoList>
<TodoFooter v-on:removeAll="removeItems"></TodoFooter>
</div>
</template>
- <TodoInput>
- v-on:addItem → 하위 컴포넌트인 TodoInput.vue에서 전달한 이벤트 시그널을 감지하고 PracticeTodo.vue에 정의된 함수를 호출하도록 구현
- <TodoList>
- v-on:removeItem → 하위 컴포넌트인 TodoList.vue에서 전달한 이벤트 시그널을 감지하고 PracticeTodo.vue에 정의된 함수를 호출하도록 구현
- v-bind:propsdata → 하위 컴포넌트인 TodoList.vue에 전달할 데이터 정의
- <TodoFooter>
- v-on:removeAll → 하위 컴포넌트인 TodoFooter.vue에서 전달한 이벤트 시그널을 감지하고 PracticeTodo.vue에 정의된 함수를 호출하도록 구현
<script>
<script>
...
export default {
data: function() {
return {
todoItems: []
}
},
created: function() {
for (let idx = 0; idx < localStorage.length; idx++) {
let key = localStorage.key(idx);
let value = localStorage.getItem(key);
this.pushItem(key, value);
}
},
methods: {
pushItem: function(key, value) {
let todoItem = {
key: key,
value: value
};
this.todoItems.push(todoItem);
console.log("Push Item: " + todoItem.key + " | " + todoItem.value);
},
addItem: function(newItem) {
let todoId = Date.now();
this.pushItem(todoId, newItem);
localStorage.setItem(todoId, newItem);
},
removeItem: function(todoItem, idx) {
localStorage.removeItem(todoItem.key);
this.todoItems.splice(idx, 1);
console.log("Remove Item: (idx|key|value)" + idx + "|" + todoItem.key + "|" + todoItem.value);
},
removeItems: function() {
console.log("Remove All Items");
localStorage.clear();
this.todoItems = [];
}
},
...
};
</script>
- data 영역
- todoItems
- 화면에서 보여질 Todo Items를 지니고 있는 변수
- 배열의 각각의 요소로 객체를 지니고 있으며 Local Storage와 동일한 구조를 지니기 위함
- todoItems
- created 영역
- 뷰 인스턴스의 라이프 사이클(Life Cycle) 속성으로 화면에 접근할 수는 없으나 화면에 표시할 데이터 전처리 구현
- methods 영역
- pushItem → 전달받은 Id와 텍스트를 객체에 저장하여 todoItems에 저장
- addItem
- todoId
- 사용자가 입력한 텍스트가 저장될 객체의 key
- Unique value로 지정되어야 하기 때문에 'Date.now()' 코드 사용
- 사용자가 입력한 텍스트와 todoId를 todoItems에 객체로 저장될 수 있도록 pushItem 호출 및 값 전달
- todoId
- removeItem → 삭제 버튼이 클릭된 Todo Item의 정보를 이용하여 Local Storage와 todoItems에서 제거
- removeItems → Local Storage와 화면에 표시할 데이터인 todoItems를 초기화
PracticeTodo.vue의 전체 코드가 궁금하신 분들은 아래 더보기를 참조해주세요.
<!-- PracticeTodo.vue -->
<template>
<div id="todo">
<TodoHeader></TodoHeader>
<TodoInput v-on:addItem="addItem"></TodoInput>
<TodoList v-bind:propsdata="todoItems" v-on:removeItem="removeItem"></TodoList>
<TodoFooter v-on:removeAll="removeItems"></TodoFooter>
</div>
</template>
<script>
import TodoHeader from "../../components/Practice/Todo/TodoHeader.vue";
import TodoInput from "../../components/Practice/Todo/TodoInput.vue";
import TodoList from "../../components/Practice/Todo/TodoList.vue";
import TodoFooter from "../../components/Practice/Todo/TodoFooter.vue";
export default {
data: function() {
return {
todoItems: []
}
},
created: function() {
for (let idx = 0; idx < localStorage.length; idx++) {
let key = localStorage.key(idx);
let value = localStorage.getItem(key);
this.pushItem(key, value);
}
},
methods: {
pushItem: function(key, value) {
let todoItem = {
key: key,
value: value
};
this.todoItems.push(todoItem);
console.log("Push Item: " + todoItem.key + " | " + todoItem.value);
},
addItem: function(newItem) {
let todoId = Date.now();
this.pushItem(todoId, newItem);
localStorage.setItem(todoId, newItem);
},
removeItem: function(todoItem, idx) {
localStorage.removeItem(todoItem.key);
this.todoItems.splice(idx, 1);
console.log("Remove Item: (idx|key|value)" + idx + "|" + todoItem.key + "|" + todoItem.value);
},
removeItems: function() {
console.log("Remove All Items");
localStorage.clear();
this.todoItems = [];
}
},
components: {
TodoHeader,
TodoInput,
TodoList,
TodoFooter
}
};
</script>
<style scoped>
#todo {
background-color: white;
}
</style>
src\components\Practice\Todo{Header, Footer, Input, List}.vue
투두 리스트(Todo List)의 하위 컴포넌트로 각 영역별 Layout 및 기능, CSS 효과를 구현하는 파일입니다.
보다 상세하게 다루기 위해 별도의 게시글로 작성하였으니 관련 코드는 아래 링크를 참조해주세요.
https://logs-jejustone.tistory.com/40
References
- HTML Tutorial: https://www.w3schools.com/html/default.asp
- Vue.js 공식 사이트: https://vuejs.org/guide/quick-start.html

'Programming > Vue' 카테고리의 다른 글
"Module not found: Error: Can't resolve '@popperjs/core' in" 오류 해결 방법 (0) | 2022.12.27 |
---|---|
[실습] 뷰(Vue)로 투두 리스트(Todo list) 구현하기 - 웹(WEB)에 투두 리스트 구현하기 (0) | 2022.12.26 |
[실습] 뷰(Vue)로 투두 리스트(Todo list) 구현하기 - 하위 컴포넌트 기능 구현하기 (0) | 2022.12.22 |
[실습] 뷰(Vue)로 투두 리스트(Todo list) 구현하기 - UI, 레이아웃(Layout) 구성/구현하기 (0) | 2022.12.21 |
[실습] 뷰(Vue)로 투두 리스트(Todo list) 구현하기 - 기획/설계하기 (0) | 2022.12.19 |
댓글