본문 바로가기

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

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

 

 

 

6장 - 6

 

 

 


06-6 더 나은 사용자 경험을 위한 기능 추가하기

  • 앞서 제작한 기능에 이어 사용자 경험을 향상시키는 2가지 기능을 추가로 구현해 보자.
  • 할 일 목록이 갑자기 생기거나 사라지거나 하는 것을 애니메이션이나 모달을 이용하여 애플리케이션의 완성도를 높여 보자.

 

<뷰 애니메이션>

  • 뷰 애니메이션은 뷰 프레임워크 자체에서 지원하는 애니메이션 기능으로, 데이터 추가, 변경, 삭제에 대해서 페이드 인(fade in), 페이드 아웃(fade out)등의 여러 가지 애니메이션 효과를 지원한다.
  • 간단한 데이터부터 목록 아이템까지 모두 지원할 뿐만 아니라 기타 자바스크립트 애니메이션 라이브러리나 CSS 애니메이션 라이브러리도 같이 사용할 수 있다.

 

  • TodoList 컴포넌트의 할 일 목록에 애니메이션을 추가하기 위해 <template> 코드를 약간 변경해보자.
<!-- TodoList.vue -->
<template>
    <section>
        <transition-group name="list" tag="ul">
            <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>
        </transition-group>
    </section>
</template>
  • 기존 <ul> 태그를 제거하고 <transition-group> 태그를 추가한다.
  • <transition-group>은 목록에 애니메이션을 추가할 때 사용되는 태그이며, tag 속성에 애니메이션이 들어갈 HTML 태그 이름(p, ul, section 등등)을 지정하면 된다. name 속성은 이후에 추가할 CSS 클래스와 연관이 있다.
  • 그리고 <li> 태그에 v-bind:key는 :key로 간략하게 표현할 수 있다. 목록 애니메이션을 적용하려면 <transition-group> 안의 대상 태그에 :key 속성을 꼭 지정해야 한다. :key 속성에는 유일하게 구분되는 값을 넣어야 하는데 여기서는 일단 todoItem이라는 텍스트 값을 사용한다.

 

  • :key 속성은 v-for 디렉티브를 사용할 때 지정하는 것이 좋다.
    • 목록 애니메이션 이외에도 :key 속성은 v-for 디렉티브를 사용할 때 꼭 지정해 주는 것이 좋다. 뷰는 목록의 특정 아이템이 삭제되거나 추가되었을 때, 돔에서 나머지 아이템의 순서를 다시 조정하지 않고 프레임워크 내부적으로 전체 아이템의 순서를 제어한다. 이렇게 프레임워크에서 목록 아이템의 순서를 제어하는 이유는 브라우저가 돔을 조작하는 데 소요되는 시간들을 최소화하기 위해서이다.
    • 예를 들어, 돔에서 목록 순서를 제어하는 경우를 살펴보자. 목록 아이템이 1000개가 있을 때, 두 번째 목록 아이템을 지우면 나머지 998개의 아이템이 모두 한 번씩 이동을 해야 한다. 화면을 다시 그려야 하는 브라우저 입장에서는 목록 아이템이 많으면 많을 수록 렌더링 부담이 커진다. 하지만 뷰 프레임워크에서 순서를 제어하는 경우 두 번째 아이템을 삭제했을 때 나머지 목록 아이템을 움직이지 않고, 내부적으로 아이템의 순서만 재조정하여 돔 이동을 최소화한다. 따라서 브라우저에서 화면을 더 빨리 그릴 수 있다. :key 속성을 사용하면 이런 작업들을 더 효율적으로 할 수 있다.

 

  • 이제 <transition-group> 태그에 적용할 CSS 속성을 추가하자.
<!-- TodoList.vue -->
<style scoped>
    .list-enter-active, .list-leave-active {
        transition: all 1s;
    }
    .list-enter, .list-leave-to {
        opacity: 0;
        transform: translateY(30px);
    }
</style>
  • CSS 속성의 클래스를 보면 모두 앞에서 설정한 name 속성 값(list)을 접두사로 갖고 있다. 그리고 enter-active, leave-active, enter, leave-to는 클래스 이름에서 짐작할 수 있듯이 데이터가 들어오고 나가는 동작을 정의하는 CSS이다.
    • 이러한 클래스 규칙과 체계는 뷰 프레임워크 내부적으로 정의되어 있기 때문에 사용 방법에 대해 더 자세히 알고 싶다면 뷰 애니메이션 클래스 공식 문서를 참고하라.
    • https://vuejs.org/guide/built-ins/transition.html
 

Transition | Vue.js

 

vuejs.org

  • CSS 속성을 적용하고 다시 화면을 실행한 후 할 일을 추가하거나 삭제하면 할 일 아이템이 부드럽데 들어오고 나가는 애니메이션 동작을 확인할 수 있다.

 

<뷰 모달>

  • 현재 애플리케이션에서 인풋 박스에 아무 값도 넣지 않고, [+] 버튼을 누르거나 Enter를 누르면 아무런 반응이 없다.
  • 텍스트 입력 값이 없을 때의 예외 처리를 하지 않았기 때문이다. 자바스크립트의 기본 경고 창을 활용해서 예외 처리를 할 수도 있지만 좀 더 보기 좋은 UI를 위해 뷰 공식 사이트에서 제공하는 팝업 대화상자인 모달(modal)을 활용해 보자.

 

  • 먼저 components 폴더 안에 common 폴더를 만들고 Modal.vue 파일을 다음과 같이 생성한다.
  • 모달 소스 코드는 아래의 사이트에서 HTML 부분의 <transition> 태그 코드와 CSS 부분을 복사해 가져온다.
 

Examples | Vue.js

 

vuejs.org

  • 복사한 <transition> 코드는 <template> 태그에 넣고, CSS 코드는 <style> 태그에 넣는다.

 

  • 이제 모달에 표시할 헤더(header)와 푸터(footer)를 정의하는 코드를 추가한다.
  • 먼저 TodoInput.vue 파일의 <template> 코드를 보자. <span> 태그 아래에 <modal> 태그와 옵션들을 추가하여 모달이 동작할 때 표시될 정보를 정의한다.
<!-- TodoInput.vue -->
<template>
    <div class="inputBox shadow">
        <input type="text" v-model="newTodoItem" placeholder="Type what you have to do" v-on:keyup.enter="addTodo">
        <span class="addContainer" v-on:click="addTodo">
            <i class="addBtn fas fa-plus" aria-hidden="true"></i>
        </span>

        <modal :show="showModal" @close="showModal = false">
            <h3 slot="header">경고</h3> <!-- 모달 헤더 -->
            <span slot="footer" @click="showModal = false"> <!-- 모달 푸터 -->
                할 일을 입력하세요.
                <i class="closeModalBtn fas fa-times" aria-hidden="true"></i>
            </span>
        </modal>
    </div>
</template>
<!-- todoInput.vue -->
<script>
import Modal from './common/Modal.vue' // Modal.vue 불러오기

export default {
    data() {
        return {
            newTodoItem: '',
            showModal: false // 모달 동작을 위한 플래그 값
        }
    },
    methods: {
        addTodo() {
            if (this.newTodoItem !== "") {
                var value = this.newTodoItem && this.newTodoItem.trim();
                this.$emit('addTodo', value);
                this.clearInput();
            } else {
                this.showModal = !this.showModal; // 텍스트 미입력 시 모달 동작
            }
        },
        clearInput() {
            this.newTodoItem = '';
        }
    },
    components: { // 모달 컴포넌트 등록
        Modal: Modal
    }
}
</script>
<!-- Modal.vue -->
<template>
  <Transition name="modal">
    <div v-if="show" class="modal-mask">
      <div class="modal-wrapper">
        <div class="modal-container">
          <div class="modal-header">
            <slot name="header">default header</slot>
          </div>

          <!--
          <div class="modal-body">
            <slot name="body">default body</slot>
          </div>
          -->

          <div class="modal-footer">
            <slot name="footer">
              default footer
              <button
                class="modal-default-button"
                @click="$emit('close')"
              >OK</button>
            </slot>
          </div>
        </div>
      </div>
    </div>
  </Transition>
</template>

<script>
export default {
    props: {
        show: Boolean
    }
}
</script>

<style>
.modal-mask {
  position: fixed;
  z-index: 9998;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: table;
  transition: opacity 0.3s ease;
}

.modal-wrapper {
  display: table-cell;
  vertical-align: middle;
}

.modal-container {
  width: 300px;
  margin: 0px auto;
  padding: 20px 30px;
  background-color: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
  transition: all 0.3s ease;
}

.modal-header h3 {
  margin-top: 0;
  color: #42b983;
}

.modal-body {
  margin: 20px 0;
}

.modal-default-button {
  float: right;
}

/*
 * The following styles are auto-applied to elements with
 * transition="modal" when their visibility is toggled
 * by Vue.js.
 *
 * You can easily play with the modal transition by editing
 * these styles.
 */

.modal-enter-from {
  opacity: 0;
}

.modal-leave-to {
  opacity: 0;
}

.modal-enter-from .modal-container,
.modal-leave-to .modal-container {
  -webkit-transform: scale(1.1);
  transform: scale(1.1);
}
</style>
  • 기존 예제로 하니 오류가 나서 정상 동작되도록 수정 후 Modal.vue의 코드까지 첨부하였다.
  • 추가한 코드를 저장한 후 인풋 박스에 아무것도 입력하지 않고 [+] 버튼을 눌러 보면 아래와 같이 모달이 나타난다.

텍스트를 입력하지 않고 [+] 버튼을 눌렀을 때 동작하는 모달

 

  • 지금까지 앞에서 배운 뷰 인스턴스, 컴포넌트, 컴포넌트 통신, 템플릿, 뷰 CLI를 이용하여 종합 애플리케이션을 제작해 보았다.
  • 뷰 CLI로 webpack-simple 프로젝트를 생성하여 화면을 컴포넌트 기반으로 설계한 다음 각각의 컴포넌트를 제작해 컴포넌트 간의 데이터 전달까지 구현하였다.
  • 이러한 설계와 구현 절차는 실제 뷰 프레임워크로 애플리케이션을 만들 때의 절차와 동일하다.
  • 따라서 만들고자 하는 서비스의 성격에 따라 webpack 또는 webpack-simple 등의 프로젝트 템플릿을 정한 후 앞에서 진행한 흐름대로 애플리케이션을 구현하면 된다.

 

  • 그리고 현재 애플리케이션은 할 일 추가, 조회, 삭제만 가능하다.
  • 변경은 구현하지 않았는데, 현재 코드에서 어떻게 하면 할 일 목록에 등록된 데이터를 변경할 수 있을지 한번 고민해 보길 바란다.

 

 

 


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