TIL | Vue 08일 (TodoApp 할 일 완료체크, footer, 할 일 목록 코드 리팩토링)

space's pace·2022년 10월 6일
0

Vue

목록 보기
7/9

시작하기 전 전에 배운 개념 다시보기!

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 할 일 완료 체크

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))
    },
  • 두 번째 줄의 콘솔 결과로는 내가 누른 체크버튼의 해당 아이템을 확인할 수 있다.

  • 세 번째줄은 화면상의 체크 버튼이 바뀌는 모습을 확인할 수 있다. 하지만 이 값이 로컬스토리지 안에는 반영된 것이 아니다.
    로컬스토리지에 새로운 정보를 저장하는 (즉, 업데이트) API가 없기 때문에 해당아이템을 지웠다가 다시 동일하게 세팅하면서, 대신 이번에 바뀐 정보(객체)를 stringify 하는 거다!
<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>
profile
블로그 이사 준비중!

0개의 댓글