App.vue
<template>
<div id="app">
<Todo-header></Todo-header>
<todo-input v-on:addTodoItem="addOneItem"></todo-input>
<todo-list
v-bind:propsdata="todoItems"
></todo-list>
<todo-footer></todo-footer>
</div>
</template>
v-on:하위 컴포넌트에서 발생시킨 이벤트 이름="현재 컴포넌트의 메서드명"
v-on:하위 컴포넌트에서 발생시킨 이벤트 이름="addOneItem"
App.vue
<script>
export default {
data: function () {
return {
todoItems: []
}
},
methods: {
// 각각의 컴포넌트에서는 표현만 하고, 실질적인 데이터 처리는 App.vue 에서 하고 있다.
addOneItem: function (todoItem) {
// 저장하는 로직
const obj = {
completed: false,
item: todoItem,
time: getDate().time
}
localStorage.setItem(todoItem, JSON.stringify(obj))
// console.log(localStorage.setItem(todoItem, JSON.stringify(obj)))
this.todoItems.push(obj)
}
// 인스턴스가 생성되자마자 호출되는 로직
created: function () {
if (localStorage.length > 0) {
for (let i = 0; i < localStorage.length; i++) {
if (localStorage.key(i) !== 'loglevel:webpack-dev-server') {
this.todoItems.push(JSON.parse(localStorage.getItem(localStorage.key(i))))
}
}
}
},
components: {
'TodoHeader': TodoHeader,
'TodoInput': TodoInput,
'TodoList': TodoList,
'TodoFooter': TodoFooter
}
}
</script>
TodoInput.vue
<script>
export default {
data: function () {
return {
newTodoItem: '',
}
},
methods: {
addTodo: function () {
if (this.newTodoItem !== '') {
this.$emit('addTodoItem', this.newTodoItem)
this.clearInput()
}
},
clearInput: function () {
this.newTodoItem = ''
}
}
}
</script>
this.emit (자식에서 부모로 데이터를 줄 때)
App 컴포넌트 (부모) |
emit으로 올려보낸 addTodoItem(하위컴포넌트 이벤트명) 로 신호를 보내고, addOneItem(현재 컴포넌트 이벤트명) 이 실행된다.
addOneItem: function (todoItem) {
const obj = {
completed: false,
item: todoItem
}
localStorage.setItem(todoItem, JSON.stringify(obj))
// 로컬스토리지의 데이터 목록과 화면의 할 일 목록의 동기화
this.todoItems.push(obj)
하위컴포넌트에서 인자를 보냈기 때문에 인자의 값을 todoItem 로 써서 this.newTodoItem
를 맵핑시켜준다.
App.vue
<template>
<div id="app">
<Todo-header></Todo-header>
<todo-input v-on:addTodoItem="addOneItem"></todo-input>
<todo-list
v-bind:propsdata="todoItems"
v-on:removeItem="removeOneitem"
></todo-list>
<todo-footer></todo-footer>
</div>
</template>
App.vue
<script>
/..
export default {
// 로컬스토리지에서 꺼내 담을 데이터 선언
data: function () {
return {
// 모든 컴포넌트에서 동일하게 쓰는 데이터
todoItems: []
}
},
methods: {
// 각각의 컴포넌트에서는 표현만 하고, 실질적인 데이터 처리는 App.vue 에서 하고 있다.
addOneItem: function (todoItem) {
// 저장하는 로직
const obj = {
completed: false,
item: todoItem,
time: getDate().time
}
localStorage.setItem(todoItem, JSON.stringify(obj))
this.todoItems.push(obj)
},
// 인자도 똑같이 넘겨주면 된다.
removeOneitem: function (todoItem, index) {
// 로컬스토리지 - removeItem(key) : 로컬스토리지의 아이템을 삭제해주는 거 (괄호안에는 key값을 넣는다)
localStorage.removeItem(todoItem.item)
// 화면에서 리스트를 삭제해주는 거 - todoItems 배열의 splice 함수를 이용해 삭제 (배열변경을 시작할 index, 삭제할 갯수)
this.todoItems.splice(index, 1)
},
../
}
</script>
TodoList.vue
<script>
export default {
props: ['propsdata'],
methods: {
removeTodo: function (todoItem, index) {
this.$emit('removeItem', todoItem, index)
// localStorage.removeItem(todoItem)
// this.todoItems.splice(index, 1)
// 이 로직을 App컴포넌트로 전달
}
}
}
</script>
addOneItem처럼 emit으로 자식 컴포넌트에서 부모 컴포넌트로 이벤트 전달 (인자도 똑같이 넘겨준다)
removeItem에 todoItem 을 콘솔로그로 찍으면 object 형태로 찍힌다. todoItem을 그대로 넘겼는데 App.vue에서 객체로 지워버리면 정상적으로 지워지지 않는다. key랑 todoItem의 item의 값이 동일하기 때문에 지울 수 있다.
App.vue
<script>
/...
export default {
// 로컬스토리지에서 꺼내 담을 데이터 선언
data: function () {
return {
// 모든 컴포넌트에서 동일하게 쓰는 데이터
todoItems: []
}
},
methods: {
/..
toggleOneItem: (todoItem, index) => {
// todoItem.completed = !todoItem.completed => 안티패턴
// 이유 : todoItems 라는 배열을 propsdata 로 TodoList.vue 에 내려주고, 이벤트버스로 todoItem 을 다시 넘겨줌 (즉, props 로 접근된 데이터를 다시 위로 끌어올린 것)
// 해결 방법 : 이벤트버스로 데이터를 끌어올려 변경하는 게 아닌 데이터에 직접 접근하여 변경하는 게 나음
// 대체 코드 (컴포넌트의 경계를 명확하게 한 코드)
this.todoItems[index].completed = !this.todoItems[index].completed
// 로컬 스토리지의 데이터를 갱신
localStorage.removeItem(todoItem.item)
localStorage.setItem(todoItem.item, JSON.stringify(todoItem))
}
}
}
</script>
TodoList.vue
<template>
<div>
<ul>
<li v-for="(todoItem, index) in propsdata" v-bind:key="todoItem.item" class="shadow">
<i class="checkBtn fa-solid fa-check" v-bind:class="{checkBtnCompleted: todoItem.completed}" v-on:click="toggleComplete(todoItem, index)"></i>
<span v-bind:class="{textCompleted: todoItem.completed}">
{{ todoItem.item }}
</span>
../
</template>
TodoList.vue
<script>
export default {
props: ['propsdata'],
methods: {
/..
toggleComplete: function (todoItem, index) {
this.$emit('toggleItem', todoItem, index)
}
}
}
</script>