본문 바로가기

IT Study./Do it! Vue.js 입문

6장. 실전 애플리케이션 만들기 - 5

 

 

 

6장 - 5

 

 

 


06-5 기존 애플리케이션 구조의 문제점 해결하기

<현재 애플리케이션 구조의 문제점>

  • 지금까지 구현한 애플리케이션의 문제점은 다음 2가지이다.
    1. 할 일을 입력했을 때 할 일 목록에 바로 반영되지 않는 점
    2. 할 일을 모두 삭제했을 때 할 일 목록에 바로 반영되지 않는 점
  • 종합해 보면 현재 화면을 4개의 영역(컴포넌트)으로 분리해 놓았기 때문에 한 영역의 처리 결과를 다른 영역에서 잠지하지 못한다는 문제가 있다.
  • 이 문제점을 해결하는 가장 간단한 방법은 컴포넌트를 4개로 분리하지 않고 한 컴포넌트 안에서 모든 것을 처리하는 것이다.
  • 하지만 컴포넌트가 이렇게 간단한 화면만 있을 수는 없고 그러므로 컴포넌트가 복잡해지는 것은 불가피하다.
  • 단순히 '컴포넌트의 단위가 작을 수록 재활용성이 높아진다' 라는 컴포넌트의 활용 측면 보다는 지금처럼 간단한 애플리케이션일 때 컴포넌트 통신을 할 수 있어야 향후에 스스로 애플리케이션을 설계하고 구현할 수 있다.

 

<문제 해결을 위한 애플리케이션 구조 개선>

  • 현재 애플리케이션은 각각의 컴포넌트에서만 사용할 수 있는 뷰 데이터 속성(newTodoItem, todoItems)을 갖고 있다. 생각해 보면 로컬 스토리지의 todoItems, TodoInput의 newTodoItem, TodoList의 todoItems는 모두 '할 일'이라는 같은 성격의 데이터를 사용하고 있다.
  • 만약 모든 컴포넌트가 '같은 데이터 속성(할일)'을 조작한다면 화면을 매번 새로 고침해야 하는 문제점은 해결할 수 있을 것이다.

 

  • 같은 데이터 속성을 사용하기 위해 최상위(루트) 컴포넌트인 App 컴포넌트에 todoItems라는 데이터를 정의하고, 하위 컴포넌트 TodoList에 props로 전달한다.

변경된 애플리케이션의 구조

  • 이전에는 할 일 데이터 추가, 삭제를 모두 하위 컴포넌트 TodoInput, TodoList에서 하였다.
  • 이제는 뷰 데이터 속성 todoItems와 로컬 스토리지의 데이터 조회, 추가, 삭제를 모두 App 컴포넌트에서 한다.
  • 그리고 하위 컴포넌트들은 그 데이터를 표현하거나 데이터 조작에 대한 요청(이벤트 발생)만 하는 것이다.
    • 이러한 중앙 집중 관리 방식은 상태 관리 라이브러리인 뷰엑스와 비슷한 구조이다. 뷰엑스에 대한 내용은 7장을 참고하라.

 

<props와 이벤트 전달을 이용해 할 일 입력 기능 개선하기>

  • 위에서 변경한 구조를 코드에 적용해 보자.
  • 먼저 최상위 컴포넌트인 App 컴포넌트(App.vue)에 데이터 속성 todoItems를 선언한다. 그리고 뒤에서 사용할 addTodo( )메서드를 추가한다.
// App.vue
export default {
  data() { // 데이터 속성 todoItems 선언
    return {
      todoItems: []
    }
  },
  methods: {
    addTodo() {
      // 로컬 스토리지에 데이터를 추가하는 로직
    }
  },
  components: {
    'TodoHeader': TodoHeader,
    'TodoInput': TodoInput,
    'TodoList': TodoList,
    'TodoFooter': TodoFooter
  }
}
  • 선언한 todoItems 속성을 TodoList 컴포넌트에 props로 전달한다.
  • TodoInput 컴포넌트 태그에는 할 일 추가 버튼을 클릭했을 때 App 컴포넌트로 이벤트를 전달할 수 있게 v-on 디렉티브를 추가한다.
// TodoList.vue
export default {
    props: ['propsdata'],
    ...
<!-- App.vue -->
<template>
  <div id="app">
    <TodoHeader></TodoHeader>
    <TodoInput v-on:addTodo="addTodo"></TodoInput>
    <TodoList v-bind:propsdata="todoItems"></TodoList>
    <TodoFooter></TodoFooter>
  </div>
</template>
  • App.vue 파일에 todoItems 데이터 속성을 선언하고, TodoList 컴포넌트의 propsdata 속성에 props로 전달하였다.
  • 그리고 TodoInput 컴포넌트 태그에서 버튼을 클릭했을 때 발생하는 이벤트 이름을 addTodo로 정한다.
  • 해당 이벤트를 받아서 실행할 App 컴포넌트의 메서드도 addTodo( )로 한다.

 

TodoInput 컴포넌트와 TodoList 컴포넌트 수정하기

  • props와 이벤트 전달을 적용하기 위해 상위 컴포넌트의 코드를 바꿨으니 하위 컴포넌트 TodoInput과 TodoList를 수정하자.
  • 첫 번째로, TodoInput 컴포넌트의 addTodo( ) 메서드에 this.$emit('addTodo', value);를 추가한다.
  • 이벤트를 전달할 때 할 일 텍스트 값인 value 객체를 인자 값으로 전달한다. 그리고 로컬 스토리지에 데이터를 저장하는 기존 코드 localStorage.setItem(value, value);를 삭제한다.
// TodoInput.vue
addTodo() {
    if (this.newTodoItem !== "") {
        var value = this.newTodoItem && this.newTodoItem.trim();
        this.$emit('addTodo', value);
        this.clearInput();
    }
},
  • 그리고 App 컴포넌트의 addTodo( ) 메서드에 아래와 같이 추가한다.
// App.vue
addTodo(todoItem) {
  localStorage.setItem(todoItem, todoItem);
  this.todoItems.push(todoItem);
}
  • 이렇게 변경하고 나서 [+] 버튼을 클릭하면 TodoInput 컴포넌트에서 App 컴포넌트로 신호(이벤트)를 보내 App 컴포넌트의 addTodo( ) 메서드를 실행한다.
  • addTodo( ) 메서드의 인자 값 todoItem은 TodoInput 컴포넌트에서 올려 보낸 할 일 텍스트 값이다. 이 값을 로컬 스토리지에 저장하고, App 컴포넌트의 todoItems 데이터 속성에도 추가한다.

 

  • 두 번째로, TodoList 컴포넌트의 <template> 내용을 아래와 같이 수정한다.
<li v-for="(todoItem, index) in propsdata" v-bind:key="todoItem" class="shadow">
    <i class="checkBtn fas fa-check" aria-hidden="true"></i>
    {{ todoItem }}
    <span class="removeBtn" type="button" @click="removeTodo(todoItem, index)">
        <i class="far fa-trash-alt" aria-hidden="true"></i>
    </span>
</li>
  • <li> 태그에서 v-for 디렉티브의 반복 대상을 propsdata로 변경하였다.
  • 기존에는 TodoList의 데이터 속성인 todoItems였지만, 이제는 App 컴포넌트의 todoItems 데이터의 개수만큼 목록 아이템을 생성한다.
  • 위 코드를 저장하고 화면에서 할 일을 추가하면 새로 고침을 하지 않고도 목록이 갱신되는 것을 확인할 수 있다.

 

TodoList에서 불필요한 코드 제거하기

  • 넘어가기 전에 TodoList.vue의 코드를 정리하고 App.vue에 코드를 옮기도록 하겠다.
<!-- TodoList.vue -->
<script>
export default {
    props: ['propsdata'],
    methods: {
        removeTodo(todoItem, index) {
            localStorage.removeItem(todoItem);
            this.todoItems.splice(index, 1);
        }
    }
}
</script>
// App.vue
created() { // TodoList.vue에서 옮겨온 코드
    if (localStorage.length) {
        for(var i = 0; i < localStorage.length; i++) {
            this.todoItems.push(localStorage.key(i));
        }
    }
},
  • TodoList 컴포넌트에서 사용하던 데이터 속성 todoItems는 이제 불필요하므로 제거한다.
  • 그리고 컴포넌트가 생성될 때 로컬 스토리지에 저장된 데이터를 모두 불러와 배열에 담아 주던 created( ) 로직도 App 컴포넌트로 옮긴다.

 

  • 이렇게 해서 할 일을 추가했을 때 목록이 갱신되지 않던 문제는 해결하였다.

 

<이벤트 전달을 이용해 Clear All 버튼 기능 개선하기>

  • 이번에는 [Clear All] 버튼을 울렀을 떄 자동으로 화면이 갱신될 수 있도록 코드를 변경해 보겠다.
  • 앞에서 배운 이벤트 버스 방식과 이벤트 전달 방식 중 이벤트 전달 방식을 사용하자.

 

  • 컴포넌트 간 이벤트 전달은 '하위 컴포넌트에서 발생시킨 이벤트를 상위 컴포넌트에서 받아 상위 컴포넌트의 메서드를 동작시키는 것'이라고 설명했다.
  • 그러면 애플리케이션에서는 하위 컴포넌트인 TodoFooter에서 발생시킬 이벤트 이름을 removeAll로 하고, 상위 컴포넌트인 App에서 받아 실행시킬 메서드 이름을 clearAll( )로 정한다.

 

상위 컴포넌트 코드 수정하기

  • 상위 컴포넌트인 App.vue 파일에 아래의 내용을 추가한다.
<!-- App.vue -->
<TodoFooter v-on:removeAll="clearAll"></TodoFooter>
// App.vue
methods: {
    clearAll() {
        localStorage.clear();
        this.todoItems = [];
    },
  • App 컴포넌트의 clearAll( ) 메서드는 TodoFooter 컴포넌트의 [Clear All] 버튼을 클릭했을 때 로컬 스토리지의 데이터를 모두 삭제하고 todoItems의 데이터를 비운다.

 

하위 컴포넌트 코드 수정하기

  • 하위 컴포넌트(TodoFooter.vue)에 있는 [Clear All] 버튼의 클릭 이벤트 메서드 clearTodo( )를 아래와 같이 수정한다.
clearTodo() {
    this.$emit('removeAll');
}
  • [Clear All] 버튼을 클릭하면 removeAll 이벤트를 발생시켜 상위 컴포넌트(App.vue)로 전달한다. 그러면 상위 컴포넌트에서 removeAll을 받아 상위 컴포넌트에 정의된 clearAll( ) 메서드를 실행한다.
  • 여기까지 코드를 저장하고 화면의 [Clear All] 버튼을 클릭하여 모든 할 일이 정상적으로 삭제되는지 확인한다.

 

<이벤트 전달을 이용해 할 일 삭제 기능 개선하기>

  • TodoList 컴포넌트의 각 할 일 아이템을 삭제하는 로직에도 이벤드 전달 방식을 적용한다.
  • App.vue 파일과 TodoList.vue 파일의 코드를 다음과 같은 순서로 변경한다.
<!-- App.vue -->
<TodoList v-bind:propsdata="todoItems" @removeTodo="removeTodo"></TodoList>
// TodoList.vue
removeTodo(todoItem, index) {
    this.$emit('removeTodo', todoItem, index);
}
// App.vue
removeTodo(todoItem, index) {
  localStorage.removeItem(todoItem);
  this.todoItems.splice(index, 1);
},
  • <TodoList>의 @removeTodo는 앞에서 다뤘던 이벤트 전달 디렉티브인 v-on:removeTodo의 약식 문법이다.
  • 할 일 목록에서 휴지통 아이콘을 클릭하면 TodoList 컴포넌트의 removeTodo( ) 메서드에서 removeTodo라는 이벤트를 발생시켜 App 컴포넌트로 전달한다.
  • 이벤트를 전달할 때 선택한 할 일의 텍스트(todoItem)와 인덱스(index)를 같이 보낸다. 이런 방식으로 이벤트를 전달할 때 인자를 함게 전달 할 수 있다.
  • 코드를 저장하고 화면에서 할 일을 삭제했을 때 정상적으로 동작하는지 확인한다.

 

  • $emit( ) API 형식과 전달 인자의 규칙
    • 하위 컴포넌트에서 이벤트를 발생시켜 상위 컴포넌트로 신호를 보낼 때 $emit( )를 사용한다. API의 기본 형식은 $emit('이벤트 이름')이지만 $emit('이벤트 이름', 인자1, 인자2, ...)와 같은 형식으로 하위 컴포넌트의 특정 데이터를 전달할 수 있다. 다만 전달받은 인자 값은 상위 컴포넌트에서 참고용으로만 활용하고, 데이터 값은 변경하지 말아야 한다. 컴포넌트는 각자 고유한 유효 범위를 갖기 때문에 상위 컴포넌트에서 전달받은 인자 값을 갱신하더라도 하위 컴포넌트에는 반영되지 않는다.

 

  • 지금까지 컴포넌트가 분리되어 데이터를 공유하지 못했던 문제점은 컴포넌트 통신 방법(props 속성, 이벤트 전달)으로 해결하였다.

 

 

 


해당 글은 [Do it! Vue.js 입문] 책을 토대로 공부한 내용을 기록하기 위하여 작성됨.