Vue.js (9) 웹 애플리케이션
1. 할 일 관리 앱
프런트엔드 프레임워크의 기본인 TODO 앱을 만들어보며 컴포넌트 구조화와 컴포넌트 통신에 대해 연습한다.
브라우저 저장소인 로컬 스토리지를 사용한다.
TodoHeader, TodoInput, TodoList, TodoFooter 4가지로 컴포넌트 영역을 분할하여 구현한다. 여러 페이지에서 컴포넌트를 재사용하기 위해 인풋 박스, 목록, 버튼 등의 작은 역할 단위로 컴포넌트를 관리한다.
webpack-simple 프로젝트 생성한다.
index.html 파일 헤더 설정
- 반응형 웹 디자인 태그
<head>
...
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
...
</head>
viewport 메타태그는 PC웹 화면, 모바일 웹 모두 레이아웃이 깨지지 않도록 만들어준다.
width=device-width: 기기의 너비만큼 웹 페이지의 너비를 지정
initial-scale=1.0 페이지가 로딩되었을 때 줌(Zoom) 레벨을 지정
이외에 아이콘, 폰트, 파비콘 링크 등을 설정해준다.
컴포넌트 생성 및 등록
컴포넌트는 관례상 src/components 폴더에서 관리한다. 규모가 커서 기능별로 관리해야 할 경우 src/{기능}/{컴포넌트}.vue 형식으로 컴포넌트를 만들어준다.
TodoHeader.vue, TodoInput.vue, TodoList.vue, TodoFooter.vue 파일 생성 후 아래와 같이 App.vue에 컴포넌트를 등록해준다.
<template>
<div id="app">
<TodoHeader></TodoHeader>
<TodoInput></TodoInput>
<TodoList></TodoList>
<TodoFooter></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 {
components: {
'TodoHeader': TodoHeader,
'TodoInput': TodoInput,
'TodoList': TodoList,
'TodoFooter': TodoFooter
}
}
</script>
<style>
</style>
컴포넌트 생성 시 스타일 태그에 scoped 옵션을 주면 스타일이 해당 컴포넌트에만 적용된다. scoped는 뷰에서 지원하는 속성이다.
단일 책임 원칙: 함수 하나가 하나의 기능만 담당하도록 설계하는 디자인 패턴. 간단한 이벤트처리라도 기능이 다르다면 함수로 따로 구현해서 활용한다.
v-for 디렉티브를 사용 할 경우 index값은 뷰에서 내부적으로 관리되며, 아래와 같이 "index"로 접근 가능하다.
<li v-for="(todoItem, index) in todoItems" v-bind:key="todoItem" class="shadow">
...
</li>
v-bind:key / :key 속성은 v-for 디렉티브 사용 시 지정해주는게 좋다. 렌더링 효율이 좋아진다고 한다.
자바스크립트 splice(2, 1, "A", "B", "C") API → 2인덱스부터 1개 제거하고 해당 위치에 "A", "B", "C" 추가
컴포넌트 간 통신
상위 컴포넌트에서 v-on 디렉티브로 하위 컴포넌트의 $emit 이벤트 감지
상위 컴포넌트에서 v-bind로 하위 컴포넌트로 전달하고 하위 컴포넌트에서는 props 속성을 사용해서 읽을 수 있다.
<!-- App.vue -->
<template>
<div id="app">
<TodoInput v-on:addTodo="addTodo"></TodoInput>
<TodoList v-bind:propsdata="todoItems"></TodoList>
</div>
</template>
// TodoInput.vue
export default {
methods: {
insert() {
this.$emit('addTodo', value);
}
},
...
}
// TodoList.vue
export default {
props: ['propsdata'],
...
}
$emit('이벤트 이름', '인자1', '인자2', ...) 형태로 상위 컴포넌트에 데이터를 전달할 수 있다. 주의 할 점은 상위 컴포넌트에서 해당 값을 변경해도 하위 컴포넌트에는 반영되지 않는다.
뷰 애니메이션
뷰 프레임워크 자체에서 애니메이션 기능을 제공한다. 데이터 추가/변경/삭제 시 페이드인, 페이드아웃 등 여러 효과를 제공한다. 해당 기능을 사용하기 위해 <transition-group> 태그를 추가한다.
태그를 추가하게 되면 아래와 같이 {name속성값}-enter-active 와 같은 형태로 CSS를 정의할 수 있다.
<template>
<section>
<transition-group name="list" tag="ul">
...
</transition-group>
</section>
</template>
<script>
...
</script>
<style scoped>
...
.list-enter-active, .list-leave-active {
transition: all 0.5s;
}
.list-enter, .list-leave-to {
opacity: 0;
transform: translateY(30px);
}
</style>
컴포넌트 재 사용
slot을 통해 컴포넌트의 재사용성을 높일 수 있다. slot은 등록 된 하위 컴포넌트의 마크업을 확장하거나 재 정의 하는 기능으로 아래와 같이 사용한다. v-slot으로 교체되었다는데 나중에 살펴보자.
<!-- 상위 컴포넌트 -->
...
<modal v-if="showModal" v-on:close="showModal = false">
<h3 slot="header">경고</h3>
<span slot="footer" v-on:click="showModal = false">
할 일을 입력하세요.
<i class="closeModalBtn fas fa-times" aria-hidden="true"></i>
</span>
</modal>
...
<!-- 하위 컴포넌트 -->
...
<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-footer">
<slot name="footer">
default footer
<button class="modal-default-button" @click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
...