Vue.js Todo App 제작2(리팩토링)

devjune·2021년 6월 24일
0

Vue.js

목록 보기
9/36

저번 포스팅에서 Todo App을 제작하며 추가, 삭제를 하여도 화면에 즉각 반응하지 않는 문제가 있었다.
이 문제를 이번에 해결해 보도록 한다.

원인

즉각 반응하지 않는 원인이 무엇일까?
메소드 내용을 보면 모두 localStorage에 직접 추가하고 삭제한다.
즉, vue의 영역과 localStorage의 영역이 서로 다르고 localStorage의 이벤트가 vue의 영역에 공유되고 있지 않다.

그렇다면, 현재 TodoList.vue 컴포넌트에서 목록을 보여주고 있는데 결국 이벤트가 일어날 때 리스트를 갱신해야된다.
현재 컴포넌트를 보면 TodoInput.vue는 등록, TodoList는 단일 삭제, TodoFooter는 전체 삭제인데, 이벤트가 일어날 때 컴포넌트에서 각각 List를 갱신하는 것은 비효율적이다.

이 문제는 기능에 따른 컴포넌트의 분리(관심사의 분리)로 해결이 가능하다.
로직 처리가 되는 비즈니스 컴포넌트, 화면을 표현하는 프레젠트 컴포넌트로 나누어보자.

App.vue

<template>
  <div id="app">
    <TodoHeader></TodoHeader>
    <TodoInput v-on:addItem="addOneItem"></TodoInput>
    <TodoList v-bind:propsdata="todoItems" v-on:removeItem="removeOneItem" v-on:toggleItem="toggleOneItem"></TodoList>
    <TodoFooter v-on:clearAll="clearAllItems"></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 {
  data: function() {
    return {
      todoItems: []
    }
  },
  methods: {
    addOneItem: function(todoItem) {
      var obj = {completed: false, item: todoItem};
      localStorage.setItem(todoItem, JSON.stringify(obj));
      this.todoItems.push(obj);
    },
    removeOneItem: function(todoItem, index) {
      this.todoItems.splice(index, 1);
      localStorage.removeItem(todoItem.item);
    },
    toggleOneItem: function(todoItem, index) {
      todoItem.completed = !todoItem.completed;
      localStorage.removeItem(todoItem.item);
      localStorage.setItem(todoItem.item, JSON.stringify(todoItem));
    },
    clearAllItems: function() {
      this.todoItems = [];
      localStorage.clear();
    }
  },
  created: function() {
    if (localStorage.length > 0) {
      for (var 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>

여기서 이제 상위컴포넌트와 하위 컴포넌트가 통신하는 props, emit을 사용한다.
App.vue가 상위컴포넌트가 되고, Header, Input, List, Footer가 하위 컴포넌트가 된다.

TodoInput.vue

<template>
  <div class="inputBox shadow">
    <input type="text" v-model="newTodoItem">
    <span class="addContainer" v-on:click="addTodo">
      <i class="addBtn fas fa-plus" aria-hidden="true"></i>
    </span>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      newTodoItem: ''
    }
  },
  methods: {
    addTodo: function() {
      if (this.newTodoItem !== '') {
        this.$emit('addItem', this.newTodoItem);
        this.clearInput();
      }
    },
    clearInput: function() {
      this.newTodoItem = '';
    }
  }
}
</script>

addTodo 메소드가 실행되며 사용자 생성 이벤트 'addItem'을 실행시키며 인자로 newTodoItem을 전달한다.
App.vue에서는 'addOneItem' 메소드를 실행시키며 localStorage와 todoItems 데이터를 동시에 갱신시킨다.

TodoFooter.vue

<template>
  <div>
    <ul>
      <li v-for="(todoItem, index) in propsdata" class="shadow" v-bind:key="todoItem.item">
        <i class="checkBtn fas 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="removeBtn fas fa-trash-alt"></i>
        </span>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: ['propsdata'],
  methods: {
    removeTodo: function(todoItem, index) {
      this.$emit('removeItem', todoItem, index);
    },
    toggleComplete: function(todoItem, index) {
      this.$emit('toggleItem', todoItem, index);
    }
  }
}
</script>

'propsdata'로 props를 지정하고 App.vue에서 TodoList.vue로 todoItems를 넘겨준다.
data는 reactive 하기 때문에 갱신된 todoItems가 즉각 props로 전달되어 리스트가 실시간으로 랜더링 된다.
할일 완료 기능인 toggleComplete는 todoItem에 속성에 completed(boolean) 속성을 클릭으로 변경시키는 기능이다.
this.$emit으로 'toggleItem' 이벤트를 발생시키고 상위컴포넌트에서 로직을 처리한다.
'removeTodo' 역시 동일한 방법으로 처리하여 생략.

TodoList.vue

<template>
  <div>
    <ul>
      <li v-for="(todoItem, index) in propsdata" class="shadow" v-bind:key="todoItem.item">
        <i class="checkBtn fas 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="removeBtn fas fa-trash-alt"></i>
        </span>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: ['propsdata'],
  methods: {
    removeTodo: function(todoItem, index) {
      this.$emit('removeItem', todoItem, index);
    },
    toggleComplete: function(todoItem, index) {
      this.$emit('toggleItem', todoItem, index);
    }
  }
}
</script>

'propsdata'로 props를 지정하고 App.vue에서 TodoList.vue로 todoItems를 넘겨준다.
data는 reactive 하기 때문에 갱신된 todoItems가 즉각 props로 전달되어 리스트가 실시간으로 랜더링 된다.
할일 완료 기능인 toggleComplete는 todoItem에 속성에 completed(boolean) 속성을 클릭으로 변경시키는 기능이다.
this.$emit으로 'toggleItem' 이벤트를 발생시키고 상위컴포넌트에서 로직을 처리한다.
'removeTodo' 역시 동일한 방법으로 처리하여 생략.

이렇게 관심사의 분리를 통해 컴포넌트를 나누는 작업을 완료 하였다.

출처 인프런 Vue.js 중급 강좌 - 웹앱 제작으로 배워보는 Vue.js, ES6, Vuex

profile
개발자준

0개의 댓글