본문 바로가기
Vue

Vue #9 - 할 일 관리 앱 만들기

by 로보리스 2021. 12. 12.

뷰 CLI를 이용한 프로젝트 생성


터미널에 vue create vue-todo 를 입력하여 프로젝트를 생성한다.

세팅 설정은 Vue2로 선택한다.

 

 

프로젝트 초기 설정


어썸 아이콘 CSS 설정

애플리케이션의 예쁜 UI를 위해 버튼은 일반 문자열 대신 어썸 아이콘을 활용한다. 구글의 머티리얼 아이콘보다 더 많은 종류를 제공하며 대중적으로 사용되는 아이콘 CSS다.

 

 

폰트와 파비콘 설정

애플리케이션의 예쁜 UI를 위해 버튼은 일반 문자열 대신 어썸 아이콘을 활용한다. 구글의 머티리얼 아이콘보다 더 많은 종류를 제공하며 대중적으로 사용되는 아이콘 CSS다.

 

 

컴포넌트 생성하고 등록하기


대상 컴포넌트는 TodoHeader, TodoInput, TodoList, TodoFooter 총 4개다.

 

컴포넌트 생성

프로젝트 폴더 src 폴더 밑에 components 폴더에 TodoHeader.vue, TodoInput.vue, TodoList.vue, TodoFooter.vue를 생성한다.

 

 

컴포넌트 등록

앞에서 생성한 4개의 컴포넌트를 등록한다. 애플리케이션에서 사용할 컴포넌트는 모두 최상위 컴포넌트인 App.vue에 등록한다. src/App.vue의 기존 코드 내용을 모두 지우고 아래와 같이 코드를 구성한다.

위와 같이 상위 컴포넌트에 컴포넌트를 등록한다.

 

 

컴포넌트 내용 구현하기


컴포넌트별로 구현할 기능은 아래와 같다.

 

  • TodoHeader : 애플리케이션 이름 표시
  • TodoInput : 할 일 입력 및 추가
  • TodoList : 할 일 목록 표시 및 특정 할 일 삭제
  • TodoFooter : 할 일 모두 삭제

 

애플리케이션 제목 추가하기

어떤 애플리케이션인지 파악하기 쉽게 애플리케이션 제목 정도만 추가한다.

CSS로 제목 꾸미기

제목의 스타일링을 위해 최상위 컴포넌트인 App.vue와 TodoHeader.vue에 다음과 같이 CSS를 추가한다.

할 일을 입력하는 TodoInput 컴포넌트

먼저 텍스트 값을 입력받기 위한 <inpiut> 태그와 텍스트 값을 저장하기 위한 <button> 태그를 추가한다. <button> 태그의 이름은 '추가'로 지정한다.

텍스트를 저장하기 위한 버튼 이벤트 추가

인풋 박스에 입력한 텍스트 값을 뷰에서 인식할 수 있게 되었다. 이제 입력한 텍스트 값을 데이터 저장소인 로컬 스토리지에 저장하면 된다. '추가' 버튼을 클릭했을 때 특정 동작을 수행할 수 있게 v-on:click 버튼 이벤트 addTodo를 지정한다.

입력받은 텍스트를 로컬 스토리지에 저장하기

로컬 스토리지의 setItem() API를 이용하여 저장한다. setItem()은 로컬 스토리지에 데이터를 추가하는 API다. API 형식은 키, 값 형태이며 저장 기능을 최대한 단순하게 하기 위해 키, 값 모두 입력받은 텍스트로 지정한다.

addTodo() 안에 예외 처리 코드 넣기

인풋 박스에 입력된 텍스트가 없을 경우 로컬 스토리지에 데이터가 저장되지 않게 예외 처리 코드를 추가해본다.

아이콘 이용해 직관적인 버튼 모양 만들기

인풋 박스와 버튼을 다루기 편하게 약간의 CSS 스타일링을 준다. 현재 버튼은 <button> 태그와 '추가'라는 텍스트를 사용하고 있다. 앞에서 설치한 어썸 아이콘의 + 아이콘을 이용하면 더 직관적인 버튼 모양을 만들 수 있다.

 

저장된 할 일 목록을 표시하는 TodoList 컴포넌트

TodoList 컴포넌트는 앞에서 저장한 일 목록을 보여주는 컴포넌트다. 현재 로컬 스토리지에 저장된 할 일이 몇 개든 모두 불러와 화면에 보여준다.

 

TodoList.vue에 할 일 삭제 기능 추가하기

splice()코드를 추가한다. splice()는 배열의 특정 인덱스에서 부여한 숫자만큼 인덱스를 삭제한다. 일반적으로 자바스크립트 배열 프로그래밍에서 삭제할 때 자주 사용하는 API다.

 

 

모두 삭제하기 버튼을 포함하는 TodoFooter 컴포넌트

TodoList 컴포넌트는 앞에서 저장한 일 목록을 보여주는 컴포넌트다. 현재 로컬 스토리지에 저장된 할 일이 몇 개든 모두 불러와 화면에 보여준다.

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


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

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

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

 

이 문제점을 해결하는 가장 간단한 방법은 컴포넌트를 4개로 분리하지 않고 한 컴포넌트 안에서 데이터 저장, 조회, 삭제를 모두 처리하는 것이다. 그러면 컴포넌트 간에 처리 결과를 알려줄 필요도 없고 깔끔하게 뷰의 반응성이 적용되어 할 일 목록이 데이터의 입출력에 따라 항상 최신 상태를 유지한다.

 

하지만 과연 이게 옳은 해결책일까? 곰곰히 생각해 보면 컴포넌트 기반의 웹 앱이 커지면 커질수록 컴포넌트의 개수가 많아지는 것은 불가피하다. 따라서 컴포넌트 구조화와 통신 방법을 익히지 않으면 이렇게 간단한 화면은 물론이고 컴포넌트가 복잡해졌을 때는 구현하기가 더 어렵다.

 

App.vue

상위 컴포넌트인 App.vue에서 처리할 수 있도록 위와 같이 props와 이벤트 전달을 추가한다.

 

TodoInput.vue

TodoInput.vue에서는 addTodo()함수에서 상위 컴포넌트로 전달할 수 있도록 $emit 이벤트로 수정한다.

 

TodoList.vue에서는 불필요한 코드를 제거하고, props 속성을 추가한다.

 

 

 

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

 

모두 삭제도 마찬가지로 상위 컴포넌트에서 동작하도록 위와 같이 코드를 구성한다.

 

 

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

<TodoList>의 @removeTodo는 이벤트 전달 디렉티브인 v-on:removeTodo의 약식 문법이다.

 

 

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


뷰 애니메이션

기존에 있던 <ul> 태그를 제거하고 <transition-group> 태그를 추가한다. 그리고 <transition-group> 태그에 적용할 CSS 속성을 추가한다.

 

 

뷰 모달

현재 애플리케이션에서 인풋 박스에 아무 값도 넣지 않고, + 버튼을 누르거나 Enter를 누르면 아무런 반응이 없다.

 

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

}
</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;
  font-family: Helvetica, Arial, sans-serif;
}

.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 {
  opacity: 0;
}

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

.modal-enter .modal-container,
.modal-leave-active .modal-container {
  -webkit-transform: scale(1.1);
  transform: scale(1.1);
}
</style>

 

components 폴더 아래에 common폴더를 만들고 Modal.vue를 위와 같이 구성한다. (첨부 된 소스 참조)

TodoInput.vue에서는 구성한 Modal.vue를 import하여 사용한다.

'Vue' 카테고리의 다른 글

Vue #8 - 뷰 CLI  (0) 2021.12.12
Vue #7 - 싱글 파일 컴포넌트 체계  (0) 2021.12.12
Vue #6 - 뷰 템플릿  (0) 2021.12.12
Vue #5 - 뷰 라우터  (0) 2021.12.11
Vue #4 - 뷰 컴포넌트 통신  (0) 2021.12.11

댓글