Vue3 기초 문법 정리
Intro
안녕하세요. 김도겸입니다.
Vue3 기초 문법과 사용법에 대해 공부한 내용들을 간단히 정리해보았습니다.
0. 간단한 설치 및 프로젝트 생성
다음과 같은 코드로 vue를 설치합니다.
$ npm install vue@next
다음과 같은 코드로 vue-cli를 install 후 vue create 명령어로 프로젝트를 간단하게 생성합니다.
npm install -g @vue/cli # 또는 yarn global add @vue/cli
vue create hello-vue3
1. 인스턴스
인스턴스 개념
인스턴스란 뷰의 시작이 되는 것으로 모든 vue 앱을 만들 때 필수로 생성해야 하는 코드입니다.
인스턴스 안에는 미리 정의되어 있는 속성과 메소드들이 있기 때문에 이 기능들을 이용하여 효과적인
화면 구현이 가능합니다.
- 가장 기초적인 vue 생성 및 data binding 예시입니다.
<div id="counter">
Counter: {{ counter }}
</div>
const Counter = {
data() {
return {
counter: 0
}
}
}
Vue.createApp(Counter).mount('#counter')
html 코드로 먼저 해당 화면의 틀을 만든 후, vue 인스턴스를 생성하여 속성을 설정합니다.
data를 화면에 표시하기 위해서는 위 사진에서 보이는 것과 같이 {{ data명 }}를 사용합니다.
인스턴스 LifeCycle
인스턴스 LifeCycle(생명주기)이란 뷰의 인스턴스가 생성되어 소멸되기까지 거치는 과정을 의미합니다.
vue는 다음과 같은 과정으로 진행됩니다.
라이프 사이클 훅
라이프 사이클 훅으로 인스턴스의 특정 시점에 원하는 로직을 구현할 수 있습니다.
따라서 반드시 라이프 사이클에 대한 이해가 필요합니다.
Vue.createApp({
data() {
return { count: 1 }
},
created() {
// `this` points to the vm instance
console.log('count is: ' + this.count) // => "count is: 1"
}
})
필요한 lifecycle 훅을 입력하여 해당 주기에서 실행될 코드를 작성합니다.
2. 템플릿 문법
보간법
-
문자열
데이터 바인딩의 가장 기초가 되는 방식입니다.
Mustache(이중 중괄호)기법을 사용하여 문자열을 바인딩할 수 있습니다.<span>메시지:{{ msg }}</span>
msg 데이터가 변경될 경우 갱신됩니다. 만약 데이터가 변경되어도 갱신되지 않기를 원한다면, v-once를 사용합니다.
<span v-once>결코 변하지 않을 것입니다: {{ msg }}</span>
-
원시 HTML
이중 중괄호는 일반 텍스트로 바인딩 되므로 만약 html을 출력하기 위해서는 v-html을 사용해야합니다.
<p>이중 중괄호 사용: </p> <p>v-html 디렉티브 사용: <span v-html="rawHtml"></span></p>
data() { return { rawHtml: '<span style="color: red">This should be red.</span>' } }
-
속성
속성을 부여할 때는 이중 중괄호 기법을 사용할 수 없으므로 v-bind를 사용합니다.
기본적인 사용방법만 정리하고, 다른 여러가지 속성은 추가적으로 설명드리겠습니다.<div v-bind:id="dynamicId"></div> <button v-bind:disabled="isButtonDisabled">Button</button>
v-bind:id는 id를 부여하고, v-bind:disabled는 boolean값인 isButtonDisabled의 값에 따라
button의 활성화 여부를 결정합니다. -
JavaScript 표현식 사용
간단한 속성 키들을 템플릿에 바인딩하는 방법이 있으나, 데이터 바인딩 시에 javascript 표현식 기능을 사용할 수 있습니다.
<div>{{ number + 1 }} {{ ok ? 'YES' : 'NO' }} {{ message.split('').reverse().join('')}}</div> <div v-bind:id="'list-' + id"></div>
하지만 바인딩에서는 반드시 하나의 단일 표현식만 포함해야됩니다.
그렇기 때문에 다음과 같이 작성하면 안됩니다.<!-- 아래는 문장입니다. 표현식이 아닙니다. --> {{ var a = 1 }} <!-- 조건문은 작동하지 않습니다. 삼항 연산자를 사용해야 합니다. --> {{ if (ok) { return message } }}
-
동적 전달인자
js표현식을 활용하여 대괄호로 묶어 디렉티브 전달인자로 사용할 수 있습니다.
<a v-bind:[attributeName]="url"> ... </a>
attributeName의 값이 변경되면 속성이 변경됩니다.
다음과 같이 ‘ v- ‘로 시작하는 속성들을 디렉티브라고 합니다.
v-bind:[속성명]는 :[속성명]으로 축약해서 사용 가능하고, v-on:[속성명]은 @[속성명]으로 축약하여 사용 가능합니다.
3. Data 속성 & methods
라이프 사이클 훅 파트에서의 코드를 보시면 data()라는 함수가 보입니다.
이 함수는 하나의 객체를 반환하고, 반환한 객체의 데이터들은 모두 vue 반응형 시스템에 의해 변경이 감지되어 컴포넌트 인스턴스의 $data에 저장된 후 화면에 반영됩니다.
methods 옵션을 사용하여 컴포넌트에 필요한 메소드를 추가하여 사용할 수 있습니다.
const app = Vue.createApp({
data() {
return { count: 4 }
},
methods: {
increment() {
// `this`는 컴포넌트 인스턴스를 참조합니다.
this.count++
}
}
})
const vm = app.mount('#app')
console.log(vm.count) // => 4
vm.increment()
// vm.count에 값을 할당하면 $data.count도 갱신됩니다.
console.log(vm.$data.count) // => 5
console.log(vm.count) // => 5
메소드 내부에서 사용할 데이터는 this.{data명}을 활용하면 됩니다.
<button @click="increment">Up vote</button>
<span :title="toTitleDate(date)">
{{ formatDate(date) }}
</span>
위처럼 템플릿에서 메소드를 직접 호출하여 사용할 수도 있습니다
4. computed & watch
computed와 watch는 모두 지정한 특정 대상이 변경될 경우 이에 반응하여 선언한 함수를 실행해주는 기능입니다.
하지만 이 둘은 같다라고는할 수 없습니다.
<div id="demo">{{ fullName }}</div>
const vm = Vue.createApp({
data() {
return {
firstName: 'Foo',
lastName: 'Bar'
}
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
}
}).mount('#demo')
computed는 firstName과 lastName이 변경이 될 때를 감지하여 이미 계산되어 있는 결과를 보여줍니다.
종속 대상의 값이 변경되지 않는다면 컴포넌트가 리렌더링 되도 다시 실행되지 않습니다.
const vm = Vue.createApp({
data() {
return {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
}
},
watch: {
firstName(val) {
this.fullName = val + ' ' + this.lastName
},
lastName(val) {
this.fullName = this.firstName + ' ' + val
}
}
}).mount('#demo')
watch는 firstName과 lastName을 감시하며 변경될 때 fullName을 변경해줍니다.
쉽게 정리하자면 computed는 특정 값이 변경될 때만을 감지하여 반환하는 변수의 역할이 필요할 때 사용하는것이 좋다고 생각하고, watch 함수는 계속 값을 감시하여 어떤 로직이 실행되어야 할 때 트리거 역할을 해줄때 사용하는 것이 좋다고 생각합니다.
5. v-bind (바인딩)
v-bind는 간편하게 view 영역에서 간편하게 data를 연결하기 위해서 사용합니다.
v-bind를 주로 사용할 수 있는 대표적인 속성은 style, class, src(이미지 바인딩), href(링크 바인딩)가 있습니다.
사용법은 생각보다 간단합니다. v-bind:{속성명}도 가능하고, v-bind를 생략한 채 :{속성명}으로도 사용 가능합니다.
간단한 코드 예제를 몇가지 보여드리겠습니다.
-
:class
1) 객체로 전달하기
<div class="static" :class="{ active: isActive, 'text-danger': hasError }" > </div>
위와 같이 객체에 필드를 포함하는 형식으로 작성합니다.
data() { return { isActive: true, hasError: false } }
객체에 포함된 데이터에 따라 다음과 같이 렌더링됩니다.
<div class="static active"></div>
isActive 또는 hasError가 변경되면 그에따라 클래스 목록이 업데이트됩니다.
<div :class="classObject"></div>
data() { return { classObject: { active: true, 'text-danger': false } } }
:class에 객체를 선언하지 않고, data()에 객체를 선언하고 사용하여도 상관없습니다.
해당 구문은 computed를 사용해도 바인딩이 가능합니다.
2) 배열로 전달하기<div :class="[activeClass, errorClass]"></div>
위와 같이 배열 형식으로 클래스 목록을 작성합니다.
data() { return { activeClass: 'active', errorClass: 'text-danger' }
배열에 포함된 데이터에 따라 다음과 같이 렌더링됩니다.
<div class="active text-danger"></div>
추가적으로 조건부로 목록의 클래스를 전환하려면, 삼항 표현식을 사용하여 수행할 수 있습니다.
<div :class="[isActive ? activeClass : '', errorClass]"></div>
위와 같이 선언하면 isActive가 false일땐 errorClass만 적용되고, true일때는 activeClass를 추가로 적용됩니다.
만약 여러 조건부 클래스가 있어 보기에 너무 복잡해 보인다면,<div :class="[{ active: isActive }, errorClass]"></div>
위와 같이 배열 구문 내에서 객체 구문을 사용해 비교적 보기 쉽게 선언할 수 있습니다.
3) 컴포넌트에서 클래스 추가하기우선적으로 컴포넌트를 간단하게 만들어보겠습니다.
(컴포넌트의 자세한 사항은 컴포넌트 파트에서 설명드리도록 하겠습니다.)const app = Vue.createApp({}) app.component('my-component', { template: `<p class="foo bar">Hi!</p>` })
<div id="app"> <my-component class="baz boo"></my-component> </div>
위와 같이 컴포넌트를 선언하고, 클래스를 추가하게 되어 사용하게 되면
<p class="foo bar baz boo">Hi</p>
위처럼 template 내에 선언한 foo, bar 클래스에 컴포넌트에 선언한 baz, boo 클래스까지 추가로 렌더링됩니다.
이는 클래스 바인딩(:class)을 사용하여도 동일하게 작동됩니다.
-
:style
:style 사용법은 css를 사용하는 것과 매우 유사하기 때문에 매우 간단합니다.
1) 객체로 전달하기
객체로 전달하는 방법은 :class와 같이 두가지 방법이 있습니다.
직접 태그 내에서 객체를 선언하여 바인딩하는 방법이 있고,
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data() { return { activeColor: 'red', fontSize: 30 } }
템플릿을 더 깔끔하게 만들기 위해, 스타일 객체를 따로 만들어 바인딩하는 방법이 있습니다.
<div :style="styleObject"></div>
data() { return { styleObject: { color: 'red', fontSize: '13px' } } }
2) 배열로 전달하기배열 구문을 사용하면, 동일한 요소에 다음과 같이 여러 스타일 객체를 적용할 수 있습니다.
<div :style="[baseStyles, overridingStyles]"></div>
:class와 동일하게 :style도 삼항 연산자를 활용하여 렌더링할 수 있습니다.<div :style="[isOveride ? overridingStyles : baseStyles, textStyles ]"></div>
textSytles는 기본으로 적용되고, isOveride의 값이 true일 경우 overridStyles를, false일 경우 baseStyle를 적용합니다.
추가적으로 다중값을 선언하여 브라우저의 따라 지원하는 배열의 마지막 값만 렌더링해줄 수 있습니다.
<div :style="{ display: [ '-webkit-box', '-ms-flexbox', 'flex' ] }"></div>
-
:src, :href
:src와 :href도 사용법이 간단합니다. :src는 이미지 경로를, :href는 사이트 url을 렌더링할 수 있습니다.
<img :src="profile" /> <a :href="url"> test.com </a>
data() { return { profile: 'test:/img/profile1.jpg', url: 'www.test.com' } }
6. v-if, v-show, v-for (렌더링)
-
v-if
v-if는 조건에 따라 렌더링할 수 있습니다.
사용 방법은 간단합니다.<h1 v-if="awesome">Vue is awesome!</h1> <div v-if="Math.random() > 0.5"> Now you see me </div>
data() { return { awesome: true; } }
위와 같이 v-if 안에 들어가는 값이 boolean이어야 하고, 변수를 넣어도 가능하고, 조건식을 넣는 것도 가능합니다.
boolean을 return하는 method 또한 가능하며, computed와 watch로도 사용가능합니다.v-if를 사용하게 되면 그에 반대에 한하는 조건에 따라 렌더링 해주는 v-else와 렌더링 조건을 추가하는 v-else-if도 사용 가능합니다.
<div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div>
v-if는 디렉티브이기 때문에 하나의 엘리먼트에 추가 되어야 합니다.
따라서 div를 사용하지 않고 여러 개의 엘리먼트를 전환할 경우 template 태그를 사용하여 감쌉니다.<template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template>
-
v-show
v-shows는 v-if와 유사하게 조건에 따라 엘리먼트를 표시하는 방식이며, 사용 방법은 거의 동일합니다.
<h1 v-show="ok">Hello!</h1>
v-if와 v-show는 큰 차이가 있습니다.
v-if는 초기 렌더링 시 조건이 거짓이면 참이 될 때까지 아무 작업도 하지 않습니다.
v-show는 조건과 관계없이 무조건 렌더링되고, 조건에 따라 css display:block / display:none 속성을 전환합니다.
따라서 무언가를 자주 전환해야 한다면 v-show를 사용하는 게 좋고, 런타임 시 조건이 변경되지 않는다면 v-if를 사용하는 게 좋습니다. -
v-for
1) 배열 렌더링
v-for는 배열을 기반으로 리스트를 렌더링할 수 있습니다.
사용방법은 다음과 같습니다.<ul id="array-with-index"> <li v-for="(item, index) in items" :key="index"> - - </li> </ul>
Vue.createApp({ data() { return { parentMessage: 'Parent', items: [{ message: 'Foo' }, { message: 'Bar' }] } } }).mount('#array-with-index')
item은 items의 배열 엘리먼트의 별칭이고, index는 배열 엘리먼트의 인덱스 값입니다.
in 대신 of도 사용 가능합니다.:key에 고유값(id 또는 index)을 설정하는 것을 권장하고 있습니다. (미설정 시 에러 메시지 발생함)
v-for는 배열의 값이 추가, 삭제, 정렬, 수정될 경우에 맞춰서 렌더링됩니다.2) 객체 렌더링
v-for를 사용하여 객체의 속성을 반복할 수도 있습니다.
<ul id="v-for-object" class="demo"> <li v-for="(value, name, index) in myObject" :key="index"> . : </li> </ul> <ul id="v-for-number"> <li v-for=" n in 10" :key="n"> </li> </ul>
Vue.createApp({ data() { return { myObject: { title: 'How to do lists in Vue', author: 'Jane Doe', publishedAt: '2020-03-22' } } } }).mount('#v-for-object')
value는 속성 값, name은 속성명(키), index는 해당 속성의 인덱스값을 의미합니다.
v-for는 배열, 객체뿐만 아니라 정수도 사용 가능합니다.
v-for도 template 태그를 활용하여 여러 요소의 블록을 렌더링할 수 있으며,<ul> <template v-for="item in items"> <li></li> <li class="divider" role="presentation"></li> </template> </ul>
component에도 적용할 수 있습니다.
<div id="todo-list-example"> <form v-on:submit.prevent="addNewTodo"> <label for="new-todo">Add a todo</label> <input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat" /> <button>Add</button> </form> <ul> <todo-item v-for="(todo, index) in todos" :key="todo.id" :title="todo.title" @remove="todos.splice(index, 1)" ></todo-item> </ul> </div>
const app = Vue.createApp({ data() { return { newTodoText: '', todos: [ { id: 1, title: 'Do the dishes' }, { id: 2, title: 'Take out the trash' }, { id: 3, title: 'Mow the lawn' } ], nextTodoId: 4 } }, methods: { addNewTodo() { this.todos.push({ id: this.nextTodoId++, title: this.newTodoText }) this.newTodoText = '' } } }) app.component('todo-item', { template: ` <li> <button @click="$emit('remove')">Remove</button> </li> `, props: ['title'] }) app.mount('#todo-list-example')
단 반복할 데이터를 컴포넌트로 전달하려면 props를 사용해야 합니다. props에 대한 설명은 해당 파트에서 진행하도록 하겠습니다.
7. 이벤트 핸들링(v-on)
v-on 디렉티브는 @ 기호로 축약하여 사용하며, DOM 이벤트를 듣고 트리거 될 때와 javascript를 실행할 때 사용합니다.
<div id="basic-event">
<button @click="counter += 1">Add 1</button>
<p>The button above has been clicked times.</p>
</div>
Vue.createApp({
data() {
return {
counter: 1
}
}
}).mount('#basic-event')
메소드 이벤트
복잡한 로직의 경우 이벤트 속성 값으로 할당하는 것은 지저분하고 오류를 범할 수 있습니다.
따라서 메소드를 사용하여 이벤트 처리를할 수 있습니다.
<div id="event-with-method">
<!-- `greet`는 메소드 이름으로 아래에 정의되어 있습니다 -->
<button @click="greet">Greet</button>
</div>
Vue.createApp({
data() {
return {
name: 'Vue.js'
}
},
methods: {
greet(event) {
// 메소드 안에서 사용하는 `this` 는 Vue 인스턴스를 가리킵니다.
alert('Hello ' + this.name + '!')
// `event` 는 네이티브 DOM 이벤트입니다
if (event) {
alert(event.target.tagName)
}
}
}
}).mount('#event-with-method')
인라인 메소드 핸들러
인라인 javascript 메소드 호출도 가능합니다.
<div id="inline-handler">
<button @click="say('hi')">Say hi</button>
<button @click="say('what')">Say what</button>
</div>
Vue.createApp({
methods: {
say(message) {
alert(message)
}
}
}).mount('#inline-handler')
복합 이벤트
연산자를 사용하여 이벤트 핸들러 안에서 복합 메소드를 지정할 수 있습니다:
<button @click="one($event), two($event)">
Submit
</button>
Vue.createApp({
methods: {
one(event) {
// 첫번째 핸들러 로직...
},
two(event) {
// 두번째 핸들러 로직...
}
}).mount('#inline-handler')
이벤트 종류
이벤트는 다양한 종류의 이벤트가 있습니다.
이 문서에 모든 이벤트를 담을 수 없어 대표적이고, 자주 사용하는 이벤트만 정리하였습니다.
@click - 엘리먼트를 마우스 좌클릭 시 호출
@change - 데이터가 변할 때마다 호출(주로 form data에서 사용됨)
@input - 데이터가 입력될 때마다 호출(주로 form data에서 사용됨)
@keyup - 키보드가 눌렀다가 때졌을 때 호출 (@keydown은 키가 눌렸을 때 호출)
그 밖의 다양한 이벤트는 해당 블로그를 참고하시면 될 것 같습니다. https://leestrument.tistory.com/entry/Event-%EC%9D%98-%EB%AA%A8%EB%93%A0%EA%B2%83
이벤트 수식어
이벤트 수식어란 v-on 디렉티브에서 사용할 수 있는 다양한 수식어를 의미합니다.
수식어를 사용하여 event 객체가 가지고 있는 메소드들을 간편하게 약어 형식으로 사용한다고 보시면 됩니다.
수식어의 종류를 간단하게 표로 정리하였습니다.
키 수식어, 명령어
이 밖에도 crtl, alt, shift, meta(윈도우 키[window], command키[mac]) 등도 있습니다.
마우스 버튼 수식어도 존재합니다. { .left, .right, .middle }
.exact 수식어
.exact 수식어를 사용하면 다른 시스템 수식어와 정확한 조합으로 눌러 이벤트가 실행되도록 해줍니다.
<!-- Alt 또는 Shift와 함께 눌린 경우에도 실행됩니다. -->
<button @click.ctrl="onClick">A</button>
<!-- Ctrl 키만 눌려있을 때만 실행됩니다. -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 아래 코드는 시스템 키가 눌리지 않은 상태인 경우에만 작동합니다. -->
<button @click.exact="onClick">A</button>
8. form 입력 바인딩
v-model 디렉티브를 사용하여 폼 입력 요소들에 양방향 데이터 바인딩(데이터의 값과 엘리먼트의 값과 상호 반영됨)을 생성할 수 있습니다.
요소에 따른 사용 방법
1) input text, textarea태그는 value 속성과 input 이벤트를 사용합니다.
<input v-model="message" placeholder="여기를 수정해보세요" />
<p>메시지: </p>
<span>여러 줄을 가지는 메시지:</span>
<p style="white-space: pre-line;"></p>
<br />
<textarea v-model="message" placeholder="여러줄을 입력해보세요"></textarea>
<!-- textarea에서는 텍스트 보간이 작동하지 않습니다. -->
<!-- bad -->
<textarea></textarea>
<!-- good -->
<textarea v-model="text"></textarea>
2) 체크박스와 라디오 버튼은 checked 속성과 change 이벤트를 사용합니다.
하나의 체크박스는 단일 boolean 값을 가지고, 여러 개의 체크박스는 동일한 v-model을 할당하고, value 값을 설정하면, 선택된 체크박스들의 value가 v-model에 바인딩됩니다.
(이때 v-model은 배열 타입이어야 합니다.)
<div id="v-model-multiple-checkboxes">
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">Mike</label>
<br />
<span>Checked names: </span>
</div>
라디오 버튼은 여러 개중에서도 하나만 선택할 수 있기에 배열이 아닌 선택된 라디오의 value값이 v-model에 바인딩됩니다.
<div id="v-model-radiobutton">
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<br />
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
<br />
<span>Picked: </span>
</div>
3) select 태그는 value를 prop으로, change를 이벤트로 사용합니다.
- 단일 셀렉트
<div id="v-model-select" class="demo">
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: </span>
</div>
- 다중 셀렉트
<div id="v-model-select" class="demo">
<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br />
<span>Selected: </span>
</div>
체크박스, 라디오, 셀렉트 v-model의 경우 문자열(또는 checkbox의 boolean)의 바인딩 값을 가지는데 입력 값이 문자열이 아닌 값을 원할 때는 다음과 같이 v-bind:value를 사용할 수 있습니다.
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
<p></p>
<input type="radio" v-model="pick" v-bind:value="{number:123}" />
<p></p>
<select v-model="selected">
<!-- 인라인 객체 리터럴 -->
<option :value="{ number: 123 }">123</option>
</select>
<p></p>
v-model도 수식어가 있습니다. text type input에서 사용합니다.
.lazy - 입력된 값이 바로 바인딩되지 않고, 엔터를 누르거나 포커스가 벗어날때 이벤트 발생
.number - 입력시 모두 문자열로 들어가게 되는데, 숫자로 변경해야 될 경우 사용
.trim - 입력한 내용에서 앞뒤 공백을 제거 처리해야 할 경우 사용
9. 컴포넌트
컴포넌트 개념
컴포넌트는 조합하여 화면을 구성할 수 있는 특정 화면 영역을 의미합니다.
컴포넌트를 사용하게 되면 빠르게 구조화하여 일괄적인 패턴으로 개발이 가능하고, 하나의 vue 파일에 많은 요소들이 들어가야 할 때 분리하여 개발이 가능하여 관리도 편하며,
반복적인 재사용도 가능합니다.
컴포넌트 사용 방법
-
vue 파일 내부에서 생성하여 등록하기
1)전역 등록
const app = Vue.createApp({}) app.component('component-a', { template: '<div>Componenet C</div>` }) app.component('component-b', { template: '<div>Componenet C</div>` }) app.component('component-c', { template: '<div>Componenet C</div>` }) app.mount('#app')
<div id="app"> <component-a></component-a> <component-b></component-b> <component-c></component-c> </div>
2)지역 등록
const ComponentA = { template: '<div>Componenet A</div>` } const ComponentB = { template: '<div>Componenet B</div>` } const ComponentC = { template: '<div>Componenet C</div>` } const app = Vue.createApp({ components: { 'component-a': ComponentA, 'component-b': ComponentB } })
-
component vue 파일을 따로 생성하여 등록하기 (모듈 시스템 활용) Babel이나 Webpack 같은 모듈 시스템을 사용할 경우 컴포넌트 파일을 따로 만들어 import해와서 사용하면 됩니다.
import ComponentA from './ComponentA' import ComponentC from './ComponentC' export default { components: { ComponentA, ComponentC } // ... }
컴포넌트 통신 방법
컴포넌트를 사용할때 부모 컴포넌트에 자식 컴포넌트를 삽입하여 사용합니다. 이때 부모 컴포넌트와 자식 컴포넌트와의 데이터를 주고 받아야 하는 경우가 있는데, 이때 Props, Emit을 사용하여 데이터를 주고 받습니다.
-
Props
Props는 컴포넌트에 등록할 수 있는 커스텀 속성입니다. 값이 prop 속성에 전달되면, 그 값은 해당 컴포넌트 인스턴스의 속성이 됩니다.
<div id="blog-posts-demo"> <blog-post v-for="post in posts" :key="post.id" :title="post.title" ></blog-post> </div>
const App = { data() { return { posts: [ { id: 1, title: 'My journey with Vue' }, { id: 2, title: 'Blogging with Vue' }, { id: 3, title: 'Why Vue is so fun' } ] } } } const app = Vue.createApp(App); app.component('blog-post', { props: ['title'], template: `<h4></h4>` }) app.mount('#blog-posts-demo')
예제 코드를 보시면 blog-post라는 컴포넌트에 posts의 title 값을 전달하기 위해 props를 활용하여 전달하였습니다. (:{props명} 활용)
이처럼 props는 부모 컴포넌트에서 자식 컴포넌트에게 필요한 데이터를 전해주는 역할을 합니다. -
Event Emit
emit은 자식컴포넌트에서 발생한 이벤트를 부모 컴포넌트에서 접근하여 사용할 수 있도록 해주는 역할을 합니다.
<div id="blog-posts-events-demo" class="demo"> <div :style="{ fontSize: postFontSize + 'em' }"> <blog-post v-for="post in posts" :key="post.id" :title="post.title" @enlarge-text="postFontSize += 0.1" ></blog-post> </div> </div>
const app = Vue.createApp({ data() { return { posts: [ { id: 1, title: 'My journey with Vue'}, { id: 2, title: 'Blogging with Vue'}, { id: 3, title: 'Why Vue is so fun'} ], postFontSize: 1 } } }) app.component('blog-post', { props: ['title'], template: ` <div class="blog-post"> <h4></h4> <button @click="$emit('enlargeText')"> Enlarge text </button> </div> ` }) app.mount('#blog-posts-events-demo')
예제를 보시면 blog-post 컴포넌트의 버튼의 click event가 emit으로 이벤트명을 넘겨주므로,
부모 컴포넌트에서 @를 사용하여 이벤트를 설정하면 blog-post 컴포넌트의 버튼 클릭 시 부모 컴포넌트에서 설정해놓은 이벤트가 발생합니다.
Slots
slot은 부모 컴포넌트에서 특정 엘리먼트나 컨텐츠를 자식 컴포넌트에 전달하고 싶을 때 사용합니다.
<div id="slots-demo" class="demo">
<alert-box>
Something bad happened.
</alert-box>
</div>
app.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
템플릿 Refs
때때로 자식 요소에 javascript를 이용하여 직접 접근해야 하는 경우가 있는데, 이럴때 사용하는 것이 ref 속성입니다.
<input ref="input" />
const app = Vue.createApp({})
app.component('base-input', {
template: `
<input ref="input" />
`,
methods: {
focusInput() {
this.$refs.input.focus()
}
},
mounted() {
this.focusInput()
}
})
위 예제는 컴포넌트가 마운트 되었을 때 input에 focus효과를 부여합니다.
동적 컴포넌트
컴포넌트 태그를 자신이 정의한 컴포넌트명이 아닌 그냥 component tag를 사용하고, is 속성을 사용하면 됩니다.
<div id="dynamic-component-demo" class="demo">
<button
v-for="tab in tabs"
:key="tab"
@click="currentTab = tab"
>
</button>
<component :is="currentTabComponent"></component>
</div>
const app = Vue.createApp({
data() {
return {
currentTab: 'Home',
tabs: ['Home', 'Archive']
}
},
computed: {
currentTabComponent() {
return 'tab-' + this.currentTab.toLowerCase()
}
}
})
app.component('tab-home', {
template: `<div class="demo-tab">Home component</div>`
})
app.component('tab-archive', {
template: `<div class="demo-tab">Archive component</div>`
})
app.mount('#dynamic-component-demo')
위 예제는 버튼 클릭 시 해당 컴포넌트를 보여주는 코드입니다. computed 속성인 currentTabComponent가 클릭한 버튼의 tab명을 가지고와서 tab-[tabName]화, component 태그의 is속성에 해당 computed 속성을 부여하면 같은 이름을 가진 컴포넌트를 component 태그에 붙여줍니다.
컴포넌트 사용 시 주의 사항
컴포넌트를 작성하거나 사용할때 권장하거나, 주의해야 할 사항을 3가지로 추렸습니다.
1) 컴포넌트 명은 kebap-case로 작성합니다.
2) 자바스크립트 내에서 prop명과 이벤트 핸들러 파라미터는 camelCase로 작성하지만 HTML 내에서 kebab-case로 해당 항목을 사용합니다.
3) ul, ol, table, select와 같은 일부 HTML 엘리먼트는 내부에 특정 엘리먼트(li, tr, option)만 표시될 수 있으므로, 컴포넌트 태그를 사용하게 되면 에러가 발생합니다.
이때는 v-is 특수 디렉티브를 사용하여 문제를 해결할 수 있습니다.
<!-- v-is의 값은 자바스크립트 문자열 리터럴이어야 함 -->
<!-- 잘못됨. 아무것도 렌더링 되지 않음 -->
<tr v-is="blog-post-row"></tr>
<!-- 올바름-->
<tr v-is="'blog-post-row'"></tr>
컴포넌트는 설명드린 기초적인 부분 이외에 각각의 요소의 세부적인 사항이 많습니다.
컴포넌트에 대한 심화적인 부분을 보고싶으시다면 아래의 사이트를 참고하시면 좋을 것 같습니다.
Vue 공식 가이드 컴포넌트 상세 설명
10. 트랜지션 & 애니메이션
트랜지션과 애니메이션은 css를 활용하여 화면에 시각적 효과를 부여합니다.
vue에서는 이 작업에 도움이 되는 컴포넌트 및 스타일 클래스를 제공합니다.
transition 컴포넌트를 활용하여 다른 컴포넌트들의 진입/진출에 특정 애니메이션 효과를 부여합니다.
transition-group 컴포넌트를 활용하여 여러 엘리먼트들을 한번에 묶어서 바로 효과를 부여합니다.
진입 / 진출
진입/진출 트랜지션에는 6가지 클래스가 적용됩니다.
.v-enter-from: enter의 시작 상태. 엘리먼트가 삽입되기 전에 적용되고 한 프레임 후에 제거됩니다.
.v-enter-to: 진입 상태의 끝에서 실행됩니다. 엘리먼트가 삽입된 후(동시에v-enter-from 가 제거됨) 트랜지션/애니메이션이 끝나면 제거되는 하나의 프레임을 추가했습니다.
.v-enter-active: enter의 활성 상태. 전체 진입 단계 동안 적용됩니다. 엘리먼트가 삽입되기 전에 적용됩니다. 트랜지션 / 애니메이션이 완료되면 제거됩니다.
.v-leave-from: leave를 위한 시작 상태. 진출 트랜지션이 트리거 될 때 적용되고 한 프레임 후에 제거됩니다.
.v-leave-to: 진출 상태 끝에서 실행됩니다. 진출 트랜지션이 트리거되고 (동시에 v-leave-from 가 제거됨) 트랜지션 / 애니메이션이 끝나면 제거되는 하나의 프레임을 추가했습니다.
.v-leave-active: leave의 활성 상태. 전체 진출 상태에서 적용됩니다. 진출 트랜지션이 트리거되면 적용되고 트랜지션 / 애니메이션이 완료되면 제거됩니다.
타이밍과 효과를 잘 이용해서 더욱 보기 좋은 화면을 만들어 낼 수 있습니다. 아래 예제는 p태그를 조건부 렌더링할때 트랜지션 효과를 주는 간단한 예제입니다.
<div id="demo">
<button @click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
const Demo = {
data() {
return {
show: true
}
}
}
Vue.createApp(Demo).mount('#demo')
/*위에서 설명한 트랜지션 클래스의 'v-' 부분을 '{트랜지션명}-' 으로 바꿔서 사용한다.'*/
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
애니메이션을 부여하는 것도 동일한 방식으로 구현하면 됩니다.
트랜지션과 애니매이션은 동시에 사용가능합니다.
.[transition-name]-enter-active,
.[transition-name]-leave-active {
animation: bounce-in 0.5s;
}
.[transition-name]-enter-from,
.[transition-name]-leave-to {
animation: bounce-in 0.5s reverse;
}
같은 방식으로 컴포넌트도 트랜지션 부여가 가능합니다.
<div id="demo" class="demo">
<input v-model="view" type="radio" value="v-a" id="a"><label for="a">A</label>
<input v-model="view" type="radio" value="v-b" id="b"><label for="b">B</label>
<transition name="component-fade" mode="out-in">
<component :is="view"></component>
</transition>
</div>
.component-fade-enter-active,
.component-fade-leave-active {
transition: opacity 0.3s ease;
}
.component-fade-enter-from,
.component-fade-leave-to {
opacity: 0;
}
.demo {
font-family: sans-serif;
border: 1px solid #eee;
border-radius: 2px;
padding: 20px 30px;
margin-top: 1em;
margin-bottom: 40px;
user-select: none;
overflow-x: auto;
}
const Demo = {
data() {
return {
view: "v-a"
};
},
components: {
"v-a": {
template: "<div>Component A</div>"
},
"v-b": {
template: "<div>Component B</div>"
}
}
};
Vue.createApp(Demo).mount("#demo");
위 예제는 라디오를 선택했을때 선택한 라디오의 value값의 만족하는 컴포넌트를 보여주는 예제입니다.
리스트
리스트 트랜지션도 유사한 방법으로 구현할 수 있습니다.
<div id="flip-list-demo">
<button @click="shuffle">Shuffle</button>
<transition-group name="flip-list" tag="ul">
<li v-for="item in items" :key="item">
</li>
</transition-group>
</div>
body {
margin: 30px;
}
.flip-list-move {
transition: transform 0.8s ease;
}
const Demo = {
data() {
return {
items: [1, 2, 3, 4, 5, 6, 7, 8, 9]
};
},
methods: {
shuffle() {
this.items = _.shuffle(this.items);
}
}
};
Vue.createApp(Demo).mount("#flip-list-demo");
위 예제는 숫자 리스트의 순서를 섞을 때 트랜지션을 부여합니다.
위 예제들은 비교적 기초적이고 간단한 예제들을 보여드렸습니다만, 트랜지션과 애니메이션을 부여하는 방법은 엄청나게 다양합니다.
능수능란하고 적절하게 이용한다면 더욱 완성도 있고 아름다운 화면을 구성할 수 있습니다.
Leave a comment