[Vue] 뷰 Todo 애플리케이션 만들기 - 2 본문

Web/Vue

[Vue] 뷰 Todo 애플리케이션 만들기 - 2

미니모아 2022. 8. 5. 14:00
반응형

Do it! Vue.js 입문을 읽고 정리한 내용입니다.

기존 애플리케이션 구조 개선하기

현재 애플리케이션 구조의 문제점은 다음과 같다.

  • 할 일을 입력했을 때 할 일 목록에 바로 반영되지 않는 점

  • 할 일을 모두 삭제했을 때 할 일 목록에서 바로 반영되지 않는 점

화면을 4개의 컴포넌트로 분리해놓았기 때문에 한 영역의 처리 결과(TodoInput, TodoFooter)를 다른 영역(TodoList)에서 감지하지 못한다는 문제가 있다.

각 컴포넌트에 있는 같은 성격의 '할일' 이라는 데이터를 최상위 컴포넌트(App 컴포넌트)에 정의하고 하위 컴포넌트들에서 props로 전달하여 사용하도록 구조를 개선해볼 수 있다.

뷰 데이터 속성 todoItems와 로컬 스토리지 관리를 App 컴포넌트에서 한다.

하위 컴포넌트들을 그 데이터를 표현하거나 데이터 조작에 대한 요청(이벤트 발생)만 한다.

할 일 입력 기능 개선하기

App에 todoItems, addTodo 추가하기

app.vue에 todoItems 데이터를 선언하고 새로운 할 일 추가를 위한 addTodo 메소드를 추가한다.

data() {
    return {
      todoItems: [],
    }
  },
  methods: {
    addTodo(todoItem) {
      localStorage.setItem(todoItem, todoItem);
      this.todoItems.push(todoItem);
    }
  },

하위 컴포넌트와 연결하기

  • TodoInput : todoItems 데이터를 props로 App 컴포넌트로부터 전달 받는다

  • TodoList: App 컴포넌트에 addTod 이벤트를 요청한다.

App.vue

<template>
  <div id="app">
    <TodoHeader/>
    <TodoInput @addTodo="addTodo"/>
    <TodoList :propsData="todoItems"/>
    <TodoFooter/>
  </div>
</template>

<script>
export default {
  methods: {
    addTodo() {
      if (this.newTodoItem !== "") {
        const value = this.newTodoItem && this.newTodoItem.trim();
        this.$emit('addTodo', value);
        this.clearInput();
      }
    },
    clearInput() {
      this.newTodoItem = '';
    }
  }
}
</script>

TodoInput.vue

export default {
  data() {
    return {
      newTodoItem:'',
    }
  },
  methods: {
    addTodo() {
      if (this.newTodoItem !== "") {
        const value = this.newTodoItem && this.newTodoItem.trim();
        this.$emit('addTodo', value);
        this.clearInput();
      }
    },
    clearInput() {
      this.newTodoItem = '';
    }
  }
}

TodoList.vue

<template>
  <section>
    <ul>
      <li v-for="(todoItem, index) in propsData" :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>
    </ul>
  </section>
</template>

<script>
export default {
  props: ['propsData'],
  methods: {
    removeTodo(todoItem, index) {
      localStorage.removeItem(todoItem);
      this.todoItems.splice(index, 1);
    }
  }
}
</script>

Clear All 버튼 기능 개선하기

하위 컴포넌트인 TodoFooter에서 상위 컴포넌트인 App 으로 이벤트를 전달한다.

<!-- App.vue -->
<TodoFooter @removeAll="clearAll"/>

<script>
// (...)
clearAll() {
      localStorage.clear();
      this.todoItems = [];
    }
</script>
//TodoFooter.vue
methods: {
    clearTodo() {
      this.$emit("removeAll");
    }
  }

할 일 삭제 기능 개선하기

각각의 할 일 아이템을 삭제하는 로직에도 이벤트 전달 방식을 사용하여 개선한다.

 //App.vue
 removeItem(todoItem, index) {
      localStorage.removeItem(todoItem);
      this.todoItems.splice(index, 1);
    }
//TodoList.vue
methods: {
    removeTodo(todoItem, index) {
      this.$emit('removeItem', todoItem, index);
    }
  }

사용자 경험을 위한 기능 추가하기

뷰 애니메이션

뷰 애니메이션은 뷰 프레임워크 자체에서 지우너하는 애니메이션 기능으로 데이터 추가, 변경, 삭제에 대해서 페이드 인, 페이드 아웃 등의 여러 가지 애니메이션 효과를 지원한다.

기존의 <ul> 태그를 <transition-group> 태그로 대체한다. <transitino-group>은 목록에 애니메이션을 추가할 때 사용되는 태그이며 tag 속성에 애니메이션이 들어갈 HTML 태그 이름을 지정하면 된다. name은 css 클래스와 연관이 있다.

v-for 디렉티브를 사용할 때 key 속성을 지정하는 이유

뷰는 목록의 특정 아이템이 삭제되거나 추가되었을 때, 돔에서 나머지 아이템의 순서를 다시 조정하지 않고 프레임워크 내부적으로 전체 아이템의 순서를 제어한다. 프레임워크에서 목록 아이템의 순서를 제어하는 이유는 브라우저가 돔을 조작하는 데 소요되는 시간들을 최소화하기 위해서이다.

1000개의 목록 아이템이 있을 때, 2번째 목록 아이템을 지우면 나머지 998개의 아이템이 모두 한 번씩 이동을 해야하며 브라우저의 입장에서는 렌더링 부담이 커진다. 하지만 뷰 프레임 워크에서 순서를 제어하는 경우 나머지 아이템을 움직이지 않고 내부적으로 아이템의 순서만 재조정하며 돔 이동을 최소화하므로 돔 이동을 최소화할 수 있고 브라우저에서 화면을 더 빨리 그릴 수 있다.

CSS 애니메이션 추가하기

css 속성의 클래스에 name 속성 값(list)을 접두사로 놓고 뒤에는 각각의 동작 시점을 붙여서 CSS을 설정할 수 있다.

  .list-item {
    display: inline-block;
    margin-right: 10px;
  }
  .list-move {
    transition: transform 1s;
  }
  .list-enter-active, .list-leave-active {
    transition: all 1s;
  }
  .list-enter, .list-leave-to {
    opacity: 0;
    transform: translateY(30px);

진입 / 진출 트랜지션에는 네가지 클래스가 적용된다

(공식 문서 : 진입/진출 그리고 리스트 트랜지션 — Vue.js)

  1. v-enter: enter의 시작 상태. 엘리먼트가 삽입되기 전에 적용되고 한 프레임 후에 제거됩니다.

  2. v-enter-active: enter에 대한 활성 및 종료 상태. 엘리먼트가 삽입되기 전에 적용됩니다. 트랜지션 / 애니메이션이 완료되면 제거됩니다.

  3. v-enter-to: 2.1.8 이상 버전에서 지원합니다. 진입 상태의 끝에서 실행됩니다. 엘리먼트가 삽입된 후 (동시에 v-enter가 제거됨), 트랜지션/애니메이션이 끝나면 제거되는 하나의 프레임을 추가했습니다.

  4. v-leave: leave를 위한 시작 상태. 진출 트랜지션이 트리거 될 때 적용되고 한 프레임 후에 제거됩니다.

  5. v-leave-active: leave에 대한 활성 및 종료 상태. 진출 트랜지션이 트리거되면 적용되고 트랜지션 / 애니메이션이 완료되면 제거됩니다.

  6. v-leave-to: 2.1.8 이상 버전에서 지원합니다. 진출 상태의 끝에서 실행됩니다. 진출 트랜지션이 트리거되고 (동시에 v-leave가 제거됨), 트랜지션/애니메이션이 끝나면 제거되는 하나의 프레임을 추가했습니다.

뷰 모달

인풋 박스에 아무 값도 넣지 않고, 데이터를 추가하려고 할 때 모달을 띄워보자

https://vuejs.org/v2/examples/modal.hchtml

위의 예제를 참고하여 Modal 컴포넌트를 정의한다.

<template>
    <Transition name="modal">
        <div 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>
            </div>
        </div>
    </Transition>
</template>

<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>

TodoInput에 모달 컴포넌트를 추가한 후 입력 값이 없을 때마다 showModal의 state가 토글되도록 한다.

 <Modal v-if="showModal" @close="showModal = false">
      <h3 slot="header">
        경고
      </h3>
      <span slot="body" @click="showModal = false">
        할 일을 입력하세요
        <i class="closeModalBtn fa fa-times" aria-hidden="true"></i>
      </span>
 </Modal>



 <script>
import Modal from './common/Modal.vue';

export default {
  data() {
    return {
      newTodoItem:'',
      showModal: false,
    }
  },
  methods: {
    addTodo() {
      if (this.newTodoItem !== "") {
        const value = this.newTodoItem && this.newTodoItem.trim();
        this.$emit('addTodo', value);
        this.clearInput();
      } else {
        this.showModal = !this.showModal;
      }
    },
    clearInput() {
      this.newTodoItem = '';
    }
  },
  components: {
    Modal: Modal
  }
}
</script> 
반응형
Comments