-
Vue 데이터 바인딩 실습 - TODO 앱 구현하기개발/Front-end 2021. 9. 15. 22:28728x90
이번 시간에는 이를 이용할 TODO 앱을 만들어보겠습니다.
0. 프로젝트 소개 및 컴포넌트 설계
0-1. 프로젝트 소개
이번에 실습할 프로젝트는 아래와 같습니다. (gif라서 약간 이미지가 깨집니다...)
0-2. 컴포넌트 구성
컴포넌트를 작게하는 경우 재사용성이 높아집니다. 따라서 아래와 같은 컴포넌트로 구성하겠습니다.
- TodoHeader: 제목
- TodoInput: 할일 목록 추가
- 할일 목록 타이핑하여 + 버튼 클릭 시, localStorage에 추가(할 일 목록에 추가)
- 할 일 목록 입력 후 엔터 클릭 시 할 일 목록에 추가
- TodoList: 할일 리스트
- localStorage에서 할 일 목록 데이터를 가져와서 보여주기
- 완료 유무 체크, 리스트 삭제
- TodoFooter: 모든 리스트를 없애는 버튼
0-3. 프로젝트 구현 순서
- Vue CLI 프로젝트 생성하기
- 컴포넌트 생성
- 파비콘, 아이콘, 폰트, 반응형 태그 설정
- 파비콘 생성: https://www.favicon-generator.org/
- 반응형 웹(뷰포트): https://www.w3schools.com/css/css_rwd_viewport.asp
- 아이콘(fontawesome): https://fontawesome.com/
- 구글 폰트(ubuntu): https://fonts.google.com/specimen/Ubuntu
- 각 컴포넌트 구현
- 그 외 리팩토링은 추후에 추가 예정
1. 뷰 CLI로 프로젝트 생성하기
$ npm install -g @vue/cli $ vue create vue-todo - default(vue3) 선택 $ cd vue-todo $ npm run serve
2. 컴포넌트 생성 및 등록하기
2-1 컴포넌트 생성
vue-todo/src/components 폴더에 아래와 같이 총 4개의 컴포넌트를 추가합니다.
각 컴포넌트 파일에 template태그 안에 컴포넌트 이름을 넣어줍니다. 예를 들면 TodoHeader.vue 파일은 아래와 같이 입력합니다.
<template> <div> TodoHeader </div> </template> <script> export default { } </script> <style> </style>
2-2. App.vue에 등록하기
App.vue은 상위 컴포넌트입니다. 따라서 지금까지 만든 컴포넌트들을 vue-todo/src/App.vue에 아래와 같이 컴포넌트를 임포트하고 template에 해당 컴포넌트 태그를 넣어줍니다.
<template> <div id="app"> <TodoHeader></TodoHeader> <TodoInput></TodoInput> <TodoList></TodoList> <TodoFooter></TodoFooter> </div> </template> <script> import TodoHeader from './components/TodoHeader.vue' import TodoInput from './components/TodoInput.vue' import TodoList from './components/TodoList.vue' import TodoFooter from './components/TodoFooter.vue' export default { components: { 'TodoHeader': TodoHeader, 'TodoInput': TodoInput, 'TodoList': TodoList, 'TodoFooter': TodoFooter } } </script> <style> body { text-align: center; background-color: #F6F6F6; } input { border-style: groove; width: 200px; } button { border-style: groove; } .shadow { box-shadow: 5px 10px 10px rgba(0, 0, 0, 0.03); } </style>
결과를 보면 아래와 같이 4개의 컴포넌트가 제대로 생성된 것을 확인할 수 있습니다.
3. 파비콘, 아이콘, 폰트, 반응형 태그 설정
- 파비콘 생성: https://www.favicon-generator.org/
- 반응형 웹(뷰포트): https://www.w3schools.com/css/css_rwd_viewport.asp
- 아이콘(fontawesome): https://fontawesome.com/
- 구글 폰트(ubuntu): https://fonts.google.com/specimen/Ubuntu
3-1. 반응형 웹(뷰포트) 추가
레이아웃 크기에 따라 깨지지 않기 위해서 vue-todo/public/index.html에 아래 코드를 추가합니다. 이미 되어있는 경우 넘어갑니다.
- 참고 사이트: https://www.w3schools.com/css/css_rwd_viewport.asp
- 경로: vue-todo/public/index.html
<meta name="viewport" content="width=device-width, initial-scale=1.0">
3-2. 파비콘
https://www.favicon-generator.org/ 사이트에서 생성하여 vue-todo/public/index.html에 파비콘 경로를 넣어줍니다. 이미 되어있는 경우 넘어갑니다.
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
3-3. 아이콘 & 폰트
https://fonts.google.com/ 에서 아이콘과 폰트 모두 사용하도록 합니다. vue-todo/public/index.html에 아래 코드를 추가합니다.
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
3-3-1. 폰트 바꾸는 법
https://fonts.google.com/ 접속하여 원하는 폰트 선택 후, 아래와 같이 선택한 후에 아래 소스를 복사하여 원하는 컴포넌트 폴더 style에 추가해줍니다.
예를 들면 저는 전체 컴포넌트에 적용하기 위해서 vue-todo/src/App.vue에서 추가해 줬습니다.
<style> @import url('https://fonts.googleapis.com/css2?family=Caveat&display=swap'); </style>
글씨체 적용된 모습은 아래와 같습니다.
3-4. 전체 index.html 코드
파비콘, 아이콘, 폰트, 반응형 태그 설정 모두 적용된 index.html 소스입니다.
- 경로: vue-todo/public/index.html
<!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
4. TodoHeader 구현
아래와 같이 제목을 추가 후 스타일을 넣어줍니다.
- 경로: vue-todo/src/components/TodoHeader.vue
<template> <header> <h1>Todo it!</h1> </header> </template> <style scoped> h1 { color: #2F3B52; font-weight: 900; margin: 2.5rem 0 1.5rem; } </style>
적용된 모습은 아래와 같습니다.
5. TodoInput
- 경로: vue-todo/src/components/TodoInput.vue
- 기능
- 할일 목록 타이핑하여 + 버튼 클릭 시, localStorage에 추가(할 일 목록에 추가)
- 할 일 목록 입력 후 엔터 클릭 시 할 일 목록에 추가
5-1. template 코드 설명
- v-model를 통해 데이터 바인딩을 하였습니다.
- v-on:keyup.enter로 엔더 입력 시 addTodo메서드(입력된 todo 목록이 추가) 실행이 가능하도록 하였습니다.
- v-on:click으로 해당 버튼 클릭 시 addTodo메소드가 실행됩니다.
- add으로 + 아이콘을 추가하였습니다.
<template> <div class="inputBox shadow"> <input type="text" v-model="newTodoItem" v-on:keyup.enter="addTodo"> <span class="addContainer" v-on:click="addTodo"> <i class="material-icons addBtn">add</i> </span> </div> </template>
5-2. script 코드 설명
- newTodoItem은 데이터 바인딩 시 사용됩니다.
- addTodo 메소드: newTodoItem데이터가 비지 않은 경우 localStorage에 입력한 데이터를 넣어줍니다.
- {completed:false, item: this.newTodoItem}: completed는 할 일 완료 유무, item은 할일
- setItem를 통해 localStorage에 넣어줍니다. 키는 newTodoItem, 값은 JSON.stringify(obj)로 넣어줍니다.
- localStorage 설명: https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem
- this.clearInput()를 실행하여 input에 있는 데이터를 빈값으로 바꿔줍니다.
<script> export default { data() { return { newTodoItem: "" } }, methods: { addTodo: function (){ if (this.newTodoItem != ''){ var obj = {completed:false, item: this.newTodoItem}; console.log(this.newTodoItem); // 저장하는 로직 localStorage.setItem(this.newTodoItem, JSON.stringify(obj)); // 스트링으로 변경되도록함 this.clearInput(); } }, clearInput: function (){ this.newTodoItem = ''; } } } </script>
5-3. TodoInput.vue 전체 소스
<template> <div class="inputBox shadow"> <input type="text" v-model="newTodoItem" v-on:keyup.enter="addTodo"> <span class="addContainer" v-on:click="addTodo"> <i class="material-icons addBtn">add</i> </span> </div> </template> <script> export default { data() { return { newTodoItem: "" } }, methods: { addTodo: function (){ if (this.newTodoItem != ''){ var obj = {completed:false, item: this.newTodoItem}; console.log(this.newTodoItem); // 저장하는 로직 localStorage.setItem(this.newTodoItem, JSON.stringify(obj)); // 스트링으로 변경되도록함 this.clearInput(); } }, clearInput: function (){ this.newTodoItem = ''; } } } </script> <style scoped> input:focus { outline: none; } .inputBox { background: white; height: 50px; line-height: 50px; border-radius: 5px; } .inputBox input { border-style: none; font-size: 0.9rem; } .addContainer { float: right; background: linear-gradient(to right, #62EAC6, #32CEE6); display: block; width: 3rem; border-radius: 0 5px 5px 0; } .addBtn { color: white; vertical-align: middle; } </style>
6. TodoList
- 경로: vue-todo/src/components/TodoList.vue
- 기능
- localStorage에서 할일 목록 데이터를 가져와서 보여주기
- 완료 유무 체크, 리스트 삭제
6-1. template 코드 설명
- v-for를 이용하여 todoItems(할 일 리스트)를 하나씩 꺼냅니다.
- 체크 버튼 클릭 시 완료 유무를 체크하게 되는 코드입니다.
- v-bind:class를 이용하여 todoItem.completed가 true인 경우, checkBtnCompleted 클래스를 추가하여 해당 클래스 스타일을 넣게 됩니다.(회색으로 변경& 글씨에 줄 넣기)
- v-on:click="toggleComplete(todoItem, index): 체크 버튼 클릭 시 todoItem.completed가 토글 되도록 합니다.
<span class="checkBtn material-icons" v-bind:class="{checkBtnCompleted: todoItem.completed}" v-on:click="toggleComplete(todoItem, index)">done</span>
- 휴지통 버튼 클릭 시 해당할 일 삭제
- removeTodo(todoItem, index)를 통해 해당 목록이 삭제가 됩니다.
<span class="removeBtn" v-on:click="removeTodo(todoItem, index)"> <i class="material-icons">delete</i> </span>
<template> <div> <ul> <li v-for="(todoItem, index) in todoItems" v-bind:key="todoItem" class="shadow"> <span class="checkBtn material-icons" v-bind:class="{checkBtnCompleted: todoItem.completed}" v-on:click="toggleComplete(todoItem, index)">done</span> <span v-bind:class="{textCompleted: todoItem.completed}">{{ todoItem.item }}</span> <span class="removeBtn" v-on:click="removeTodo(todoItem, index)"> <i class="material-icons">delete</i> </span> </li> </ul> </div> </template>
6-2. script 코드 설명
- removeTodo 메소드: 해당 todoItem를 localStorage에서 제거하고 todoItems 데이터에서 해당 내용을 지워줍니다. vue 수업에서는 이런식으로 지우고 새로 만드는 것으로 알려주셨지만 굳이 제거할 필요없이 setItem 메소드만으로 업데이트가 가능합니다.
- toggleComplete 메소드: 업데이트를 하기 위해 기존 todoItem를 제거하고 다시 새로 localStorage에 넣어줍니다.
- created: 인스턴스 생성 후 호출되는 곳으로 데이터 초기 선언 시 넣어줍니다. 여기서도 localStorage에 있는 데이터를 불러와서 todoItems data 리스트에 넣어줍니다. JSON.parse를 하는 이유는 string 변환한 object를 다시 object로 변환하기 위해서 사용합니다.
<script> export default { data() { return { todoItems: [] } }, methods: { removeTodo: function(todoItem, index){ console.log(todoItem, index); localStorage.removeItem(todoItem); this.todoItems.splice(index, 1); // 특정 인덱스를 지우게된다. }, toggleComplete: function(todoItem, index) { console.log(todoItem, index); todoItem.completed = !todoItem.completed; // 업데이트 - 해당 기능이 없어 삭제 후 삽입 localStorage.setItem(todoItem.item, JSON.stringify(todoItem)); } }, created: function() { if (localStorage.length > 0) { for (var i = 0; i < localStorage.length; i ++) { if (localStorage.key(i) !== 'loglevel:webpack-dev-server') { console.log(JSON.parse(localStorage.getItem(localStorage.key(i)))); this.todoItems.push(JSON.parse(localStorage.getItem(localStorage.key(i)))); } } } } } </script>
6-3. TodoList.vue 전체 소스
<template> <div> <ul> <li v-for="(todoItem, index) in todoItems" v-bind:key="todoItem" class="shadow"> <span class="checkBtn material-icons" v-bind:class="{checkBtnCompleted: todoItem.completed}" v-on:click="toggleComplete(todoItem, index)">done</span> <span v-bind:class="{textCompleted: todoItem.completed}">{{ todoItem.item }}</span> <span class="removeBtn" v-on:click="removeTodo(todoItem, index)"> <i class="material-icons">delete</i> </span> </li> </ul> </div> </template> <script> export default { data() { return { todoItems: [] } }, methods: { removeTodo: function(todoItem, index){ console.log(todoItem, index); localStorage.removeItem(todoItem); this.todoItems.splice(index, 1); // 특정 인덱스를 지우게된다. }, toggleComplete: function(todoItem, index) { console.log(todoItem, index); todoItem.completed = !todoItem.completed; // 업데이트 - 해당 기능이 없어 삭제 후 삽입 localStorage.setItem(todoItem.item, JSON.stringify(todoItem)); } }, created: function() { if (localStorage.length > 0) { for (var i = 0; i < localStorage.length; i ++) { if (localStorage.key(i) !== 'loglevel:webpack-dev-server') { console.log(JSON.parse(localStorage.getItem(localStorage.key(i)))); this.todoItems.push(JSON.parse(localStorage.getItem(localStorage.key(i)))); } } } } } </script> <style scoped> ul { list-style-type: none; padding-left: 0; margin-top: 0; text-align: left; } li { display: flex; min-height: 50px; height: 50px; line-height: 50px; margin: 0.5rem 0; padding: 0 0.9rem; background: white; border-radius: 5px; } .checkBtn { line-height: 45px; color: #62acde; margin-right: 5px; } .checkBtnCompleted { color: #b3adad; } .textCompleted { text-decoration: line-through; color: #b3adad; } .removeBtn { margin-left: auto; color: #de4343; } </style>
7. TodoFooter
- 경로: vue-todo/src/components/TodoFooter.vue
clearTodo를 클릭하면 localStorage에 내용은 모두 제거합니다.
<template> <div class="clearAllContainer"> <span class="clearAllBtn" v-on:click="clearTodo">Clear All </span> </div> </template> <script> export default { methods: { clearTodo: function() { localStorage.clear(); } } } </script> <style scoped> .clearAllContainer { width: 8.5rem; height: 50px; line-height: 50px; background-color: white; border-radius: 5px; margin: 0 auto; } .clearAllBtn { color: #e20303; display: block; } </style>
다음 시간에는 해당 소스를 리팩토링을 진행하겠습니다.
해당 내용 전체 소스는 아래 링크에서 확인할 수 있습니다.
https://github.com/JUNGEEYOU/VueTodo.git에서 todo_implement 브런치에 존재합니다.
참고
'개발 > Front-end' 카테고리의 다른 글
Vue HTTP 통신 라이브러리 - axios (0) 2021.09.03 Vue 데이터 바인딩 (0) 2021.08.25 Vue 컴포넌트 Basic (0) 2021.08.24 Vue Lazy Load(비동기 컴포넌트)란? (0) 2021.08.24 Vue Lazy load 적용하기 (0) 2021.08.24