시작하기 전 전에 배운 개념 다시보기!
state
: 여러 컴포넌트에 공유되는 데이터
// Vue data: { message: 'Hello Vue.js' } // Vuex state: { message: 'Hello Vue.js' }
template 호출
<!-- Vue --> <p>{{ message }}</p> <!-- Vuex --> <p>{{ this.$store.state.message }}</p>
TodoList.vue
// 인스턴스가 생성되자마자 호출되는 로직
created: function () {
// 로컬스토리지에 데이터가 있는지 확인
if (localStorage.length > 0) {
// 로컬스토리지에서 제공하는 전체 아이템을 한 번에 가져오는 API 없기 때문에
// for 문을 통해 반복하고 아이템에 접근하여 담을 거임
for (let i = 0; i < localStorage.length; i++) {
if (localStorage.key(i) !== 'loglevel:webpack-dev-server') {
// console.log(localStorage.key(i))
// 위 콘솔로그의 예상 값은 내가 인풋에 썼던 할일 목록들이 키에 저장되어 뜬다.
this.todoItems.push(JSON.parse(localStorage.getItem(localStorage.key(i))))
}
}
}
},
우리가 로컬스토리지에 키 값을 조회(localStorage.getItem)할 때, JSON 형태로 왜 다시 넣어야 할까?
먼저 console.log 로 값을 찍어보자
console.log(localStorage.getItem(localStorage.key(i)))
콘솔에는 겉보기에는 제대로 값이 찍힌 것처럼 보이지만 실은 'string'형태로 객체가 아니다. 위의 코드에 typeof를 넣어 데이터 형태를 확인하면 'string' 이 뜬다.
다시 조회할 때는 객체형으로 변환하기 위해 JSON.parse 메서드를 사용한다.
앞에 JSON.parse를 쓰고 콘솔로 다시 찍어보면,
객체형태로 나온다. (key- value가 있는 형태)
TodoList.vue
<template>
<div>
<ul>
<li v-for="(todoItem, index) in todoItems" 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>
<span class="removeBtn" v-on:click="removeTodo(todoItem, index)">
<i class="fa-solid fa-trash-can"></i>
</span>
</li>
</ul>
</div>
</template>
객체 안에 값에 접근하기 위해선 앞에.
을 쓰면 된다.
v-bind:class="{textCompleted: todoItem.completed}"
기존에 html 속성에다가 동적인 값을 부여
textCompleted 는 클래스 명이고, 해석하자면 todoItem.completed 속성에 따라서
todoItem.completed의 값이 false면 textCompleted 클래스가 사라지고
true면 클래스가 생김
TodoList.vue
methods
toggleOneItem: (todoItem, index) => {
// console.log(todoItem)
// todoItem.completed = !todoItem.completed
this.todoItems[index].completed = !this.todoItems[index].completed
// 로컬 스토리지의 데이터를 갱신
localStorage.removeItem(todoItem.item)
localStorage.setItem(todoItem.item, JSON.stringify(todoItem))
},
<template>
<div class="wrap">
<div class="select-wrap">
<label class="blind" for="order">filter</label>
<select name="order" id="order" v-model="selected" v-on:change="sortTodo">
<option value="date-asc">오래된 순</option>
<option value="date-desc">최신순</option>
</select>
</div>
<div class="clearAllContainer">
<span class="clearAllBtn" v-on:click="clearTodo">Clear All</span>
</div>
</div>
</template>
<script>
export default {
methods: {
sortTodo () {
this.$emit('sortItem', {value: this.selected})
},
clearTodo: function () {
localStorage.clear()
}
}
}
</script>
localStorage의 모든 데이터를 지우는 메서드를 사용해 v-on 디렉티브로 연결해준다.
첫 번째 문제점: 인풋창에 할 일을 추가하면 로컬스토리지에 데이터는 저장되지만 리스트에는 반영이 되지 않았다
그 이유는 TodoList 컴포넌트에서는 데이터가 저장된 사실을 알 수 없기 때문이다. 다시 리렌더링을 해야만 리스트가 추가된다.
두 번째 문제점: Footer 컴포넌트에서는 로컬스토리지의 데이터를 모두 지워주는 버튼을 생성했는데 이 역시 로컬스토리지에서 데이터가 지워지지만 화면에는 바로 반영이 되지 않았다. (리렌더링 해야만 반영)
= 컴포넌트 간의 데이터 전달과 통신의 문제점
App.vue 를 container (기능을 담당하는)
그 하위 컴포넌트들을 presenter (화면을 표현하는) 의 형태로 리팩토링하고자 한다.
먼저 TodoList.vue에서 작성된 created(vue 라이프사이클 중 하나로, 인스턴스가 생성되자마자 호출됨)를 App.vue로 가져온다.
<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 {
// 로컬스토리지에서 꺼내 담을 데이터 선언
data: function () {
return {
// 모든 컴포넌트에서 동일하게 쓰는 데이터
todoItems: []
}
},
// 인스턴스가 생성되자마자 호출되는 로직
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>
todoItems.push를 해야 하니 데이터를 선언해주는 부분도 List 컴포넌트에서 가져온다. 이제 이걸 TodoList로 보내줘야 한다 (props)
App.vue
<template>
<div id="app">
<Todo-header></Todo-header>
<todo-input></todo-input>
<todo-list
v-bind:propsdata="todoItems"
></todo-list>
<todo-footer></todo-footer>
</div>
</template>
v-bin:내려보낼 프롭스 속성 이름="현재 위치의 컴포넌트 데이터 속성"
= v-bind:propsdata="todoItems"
TodoList.vue
<script>
export default {
props: ['propsdata']
}
}
</script>
App.vue 에서 내려준 propsdata를 리스트 컴포넌트에서 받기 => propsdata에는 todoItems가 담겨있다. 그러면 이제 리스트 템플릿에서 v-for로 반복시켰던 배열의 이름도 바꿔줘야 한다. (todoItems를 App에서 받아온 propsdata로!)
<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>
<span class="removeBtn" v-on:click="removeTodo(todoItem, index)">
<i class="fa-solid fa-trash-can"></i>
</span>
</li>
</ul>
</div>
</template>