※ 일본의 한 블로그 글을 번역한 포스트입니다. 오역 및 의역, 직역이 있을 수 있으며 틀린 내용은 지적해주시면 감사하겠습니다.
환경
이번 포스트에서는 Vue CLI를 사용한다. 프로젝트를 생성하면 다음과 같은 디렉토리로 파일이 구성될 것이다. 기본적으로는 편집하는 것은 App.vue와 components 폴더이다. 그 외에는 디폴트 상태로 수정하지 않아도 된다.
터미널에서 다음의 커맨드를 실행하면 개발서버가 실행된다.
$ npm run serve
이 상태에서 localhost URL(http://localhost:8080/)에 액세스하면 다음과 같은 화면이 표시된다.
vue 파일을 변경하면 그 변경 내용이 이 페이지에 바로 반영된다. 테스트로 App.vue의 내용을 모두 삭제하면 아무것도 표시되지 않게 된다.
그래도 변경이 반영되지 않았다면 command + shift+R로 슈퍼 리로드를 해보자. 즉, npm run serve로 개발용 서버를 재실행하는 것은 필요하지 않다.
완전히 아무것도 없는 상태에서 설명을 시작하고자 하므로 다음과 같이 작성해두자.
- App.vue의 안을 모두 삭제
- components 폴더의 안에 들어있는 HelloWorld.vue를 삭제
vue 파일이란?
확장자가 .vue로 되어 있는 파일이다. 기본적으로는 vue 파일 하나에 하나의 컴포넌트로 구성한다. vue 파일은 주로 아래의 세 부분으로 구성되어 있다.
- template태그: 컴포넌트의 html 요소를 채워넣음
- script 태그 : javascript를 기재
- style 태그 : css를 기재
이 세가지를 합쳐 하나의 부품으로써 컴포넌트를 작성해 그러한 컴포넌트를 합쳐서 1개의 Web 페이지를 만드는 것이 Vue.js의 대략적인 발상법이다.
<template>
<!-- 여기에html를 기재한다. -->
</template>
<script>
// 여기에 javascript를 기재한다.
</script>
<style>
/* 여기에 css를 기재한다. */
</style>
컴포넌트를 사용해보기
Header.vue
components 폴더에 Header.vue를 작성하고, 그 파일에 아래와 같이 기재해보자.
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ text }}</p>
</div>
</template>
<script>
export default {
data(){
return {
title: "Header",
text: "Hello Vue.js!"
}
}
}
</script>
<style scoped>
div{
border: 1px solid blue
}
h1{
color: blue
}
p{
color:blue
}
</style>
script 태그안에 정의한 변수 title, text를 template 태그내에서 호출해서 사용하고 있다. 또한, style 태그내에 문자와 태두리를 파란색으로 하고 있다.
style 태그에 기재되어 있는 scoped는 "이 컴포넌트에서만 style을 적용한다"라는 것을 명시적으로 선언한 것이다.
Body.vue
또 components 폴더 안에 body.vue를 생성한 후 파일에 다음의 내용을 기재해보자.
<template>
<div>
<h2>{{ title }}</h2>
<p>{{ text }}</p>
</div>
</template>
<script>
export default {
data(){
return {
title: "Body",
text: "Have a good day!"
}
}
}
</script>
<style scoped>
div{
border: 1px solid red
}
h2{
color: red
}
p{
color: red
}
</style>
정의되어 있는 것은 Header.vue와 거의 동일하다. Header.vue과 구분하기 쉽도록 여기서는 문자 색을 빨간색으로 학 ㅗ있다.
App.vue
그리고 방금 만든 Header.vue와 Body.vue를 App.vue에서 호출하고 싶으므로 App.vue에서 다음과 같이 기재한다.
<template>
<div>
<Header></Header>
<Body></Body>
<Body></Body>
</div>
</template>
<script>
import Header from './components/Header.vue'
import Body from './components/Body.vue'
export default {
components: {
Header,
Body
}
}
</script>
자식 컴포넌트를 호출하기 위해 먼저 script 태그안에 자식 컴포넌트의 vue 파일을 import 하고, components 요소에 사용할 컴포넌트 명을 기재한다.
그리고 template태그 안에 자식 컴포넌트명을 그대로 태그로서 기재해주면, 그 장소에 자식 컴포넌트가 대입된다. 이번에 작성한 파일의 결과를 살펴보면 다음과 같은 화면이 된다.
맨 위가 Header 컴포넌트, 그 아래의 두 개는 Body 컴포넌트가 배치되어 있다.
컴포넌트 사이의 데이터 송신
부모 > 자식
부모쪽에서 데이터 전달 창구(태그 속성)
데이터를 전달할 창구는 자식컴포넌트를 호출하고 있는 태그내에 커스텀 속성을 정의하는 부분이다.
예를 들어 다음과 같다. username 속성을 정의하는 것으로 자식 컴포넌트쪽에 username이라는 변수로 데이터를 받을 수 있다.
<template>
<div>
<Header :username='name'></Header>
〜생략〜
</div>
</template>
<script>
〜생략〜
export default {
data(){
return {
name: "kiyokiyo"
}
},
〜생략〜
}
</script>
자식쪽에서 데이터를 받아들이는 창구(props)
자식 컴포너틑쪽에서는 props를 사용하여 데이터를 받아들일 수 있다. 부모 컴포넌트쪽에서 정의한 속성명과 데이터형을 오브젝트형으로 기재한다.
<!--Header.vue-->
<script>
export default {
props: {
username: String
},
〜생략〜
}
</script>
<!--Header.vue-->
<template>
<div>
〜생략〜
<p>Welcome! {{ username }}!</p>
</div>
</template>
이런식으로 App.vue에서 정의한 변수를 Header 컴포넌트에 전달하여 표시시키는 것이 가능하다.
자식 > 부모
Body 컴포넌트에서 각각 버튼 클릭으로 증간된 값을 App 컴포넌트에 보내, 토탈을 계산하여 표시하고 싶다고 생각한 경우, Body 컴포넌트에서의 카운트업의 기능과 App 컴포넌트에서의 토탈 표시의 기능을 먼저 만들었다. 데이터 송수신을 위한 기능은 아직 만들지 않았기에 제대로 동작되지 않는다.
<!-- App.vue -->
<template>
<div>
<Header :username='name'></Header>
<Body></Body>
<Body></Body>
<p>total : {{ totalcount }} </p>
</div>
</template>
<script>
import Header from './components/Header.vue'
import Body from './components/Body.vue'
export default {
data(){
return {
name: "kiyokiyo",
count1: 0,
count2: 0,
totalcount: 0
}
},
components: {
Header,
Body
}
}
</script>
<!-- Body.vue -->
<template>
<div>
<h2>{{ title }}</h2>
<p>{{ text }}</p>
<p><button @click="increment">+1</button> {{ count }} </p>
</div>
</template>
<script>
export default {
data(){
return {
title: "Body",
text: "Have a good day!",
count: 0
}
},
methods: {
increment(){
this.count += 1;
}
}
}
</script>
자식쪽에서 데이터 전달 창구($emit)
this.$emit으로 커스텀 이벤트를 정의해준다. 여기에서는 add 이벤트를 정의하여, 이 인수로서 카운트된 값을 전달한다.
<!--Body.vue-->
〜생략〜
<script>
export default {
〜생략〜
methods: {
increment(){
〜생략〜
this.$emit("add",this.count);
}
}
}
</script>
부모쪽에서 데이터를 받는 창구(이벤트와 함수인수)
자식 컴포넌트에서 정의한 add이벤트는 자식 컴포넌트쪽의 태그 속성에서 @add = [함수명] 으로 기재하는 것으로, methods로 정의하고 있느 함수에 전달하는 것이 가능하다.
이 함수에서 인수를정의해주면, 자식 컴포넌트에서의 값을 받아들일 수 있다. 이번의 예에서는 인수로서 받은 count를 각각 count1 혹은 count2에 저장하여, 그것들을 더해주면 totalcout가 갱신된다.
<!--App.vue-->
<template>
<div>
〜생략〜
<Body @add="add1"></Body>
<Body @add="add2"></Body>
<p>total : {{ totalcount }} </p>
</div>
</template>
<script>
〜생략〜
export default {
〜생략〜
methods:{
add1(count){
this.count1 = count;
this.totalcount = this.count1 + this.count2;
},
add2(count){
this.count2 = count;
this.totalcount = this.count1 + this.count2;
}
}
}
</script>
html 요소를 부모 > 자식에게 전달
부모쪽에서 데이터를 전달하는 창구(template v-slot)
자식 컴포넌트명의 태그 안에 template 태그를 놓고, 그 안에 html 요소를 쓰는 것으로 전달하는 것이 가능하다. v-slot:[슬롯명] 으로, 슬롯명을 지정한다. 여기서 지정한 이름을 자식 컴포넌트쪽에서 사용한다.
이번의 예에서는 1개의 template만 사용하고 있지만, 슬롯명을 바꾸면 여러 개의 template을 자식 컴포넌트에 보내는 것도 가능하다.
결과를 알기 쉽도록, style 태그로 문자색이 초록색으로 바뀌도록 하였다.
<!--App.vue-->
<template>
<div>
<Header :username='name'>
<template v-slot:message>
<p>Let's enjoy programming!</p>
</template>
</Header>
〜생략〜
</div>
</template>
〜생략〜
<style scoped>
p{
color: green
}
</style>
자식쪽에서 데이터를 받아들이는 창구(slot)
자식 컴포넌트족에서는 slot 태그를 사용하여 html 태그를 받아들인다. 이 때에는 부모 컴포넌트에서 지정한 슬롯명을 name 속성으로 기재하는 것으로 보내진 template을 그곳에 삽입하게 된다.
<!--Header.vue-->
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ text }}</p>
<slot name="message"></slot>
<p>Welcome! {{ username }}!</p>
</div>
</template>
다음과 같이 부모 컴포넌트에서 정의된 html 요소를 자식 컴포넌트에 전달하는 것이 가능하다. 어디까지나 부모 컴포넌트로 정의된 요소이므로, Header 컴포넌트에 전달된 문자색이 초록인 것을 알 수 있다.
컴포넌트 간 쌍방향 데이터 바인딩
자식쪽의 input에 입력된 값을 받아들여 자식쪽의 컴포넌트에 표시하고 싶은 경우 사용할 수 있는 방법이다.
자식에서 데이터 전달 창구($emit)
$emit으로 input에 무엇인가 입력될 때 마다 부모의 input 이벤트를 발생하도록 한다. 그 때에 input의 value 값을 인수로서 보내고 있다. 이 방법은 자식 > 부모로 데이터를 전달하는 것과 같다.
또한 부모 컴포넌트에서 value를 받아들이도록 props에 value를 선언하고 있다.
또한, 컴포넌트간의 쌍방향 바인딩의 경우, $emit의 안의 이벤트명 input과 props로 받은 변수명 value는 반드시 이 이름으로 하지 않으면 안되므로 주의하자.
<!--Header.vue-->
<template>
<div>
〜생략〜
<label for="condition">How are you?</label>
<input id="condition" type="text" :value="value" @input="$emit('input',$event.target.value)">
</div>
</template>
<script>
export default {
props: {
〜생략〜
value: String
},
〜생략〜
}
</script>
부모쪽에서 데이터를 받아들이는 창구(v-model)
v-model로 입력을 받아들이는 변수를 지정하고 있다. v-model은 내부적으로는 input 이벤트(@ivent)를 보유하고 있으며, input 이벤트가 발생할 때마다 인수로서 전달된 데이터(이번 예제에 대입하여 얘기하면 $event.target.value)로, 지정된 변수(이번의 예에서는 InputData.condition)를 변경하도록 되어있다.
또한, v-model은 v-bind:value를 내부적으로 가지고 있으므로, 지정한 변수가 부모 컴포넌트쪽에서의 처리로 바꿔 쓴 경우, 그 내용이 임의로 자식 컴포넌트에도 반영되게 된다.
자식 컴포넌트는 반드시 input 및 value를 사용하지 않으면 안되는 것은 부모 컴포넌트의 v-model에서 내부적으로 갖고 있는 명칭과 맞출 필요가 있기 때문이다.
<!--App.vue-->
<template>
<div>
<Header :username='name' v-model="InputData.condition">
〜생략〜
</Header>
〜생략〜
<p>condition : {{ InputData.condition }} </p>
</div>
</template>
<script>
〜생략〜
export default {
data(){
return {
〜생략〜
InputData: {
condition: ""
}
}
},
〜생략〜
}
</script>
컴포넌트를 동적으로 바꾸기
component태그를 이용하여 속성에 is=[컴포넌트명]으로 기재하는 것으로, 사용할 컴포너틑를 지정하는 것이 가능하다. 이것을 이용하여 componentName에 바인드하여 버튼 클릭에 의해 componentName을 동적으로 변경하도록 하여 표시할 컴포넌트도 동적으로 바뀌는 구조로 구현할 수 있다.
<!--App.vue-->
<template>
<div>
〜생략〜
<button @click="componentName = 'Header'">Header</button>
<button @click="componentName = 'Body'">Body</button>
<component :is="componentName"></component>
</div>
</template>
<script>
〜생략〜
export default {
data(){
return {
〜생략〜
componentName: "Header"
}
},
〜생략〜
}
</script>
그러나, 이러한 작성법이면 버튼이 바뀔때마다 컴포넌트가 초기화되므로, input에 입력한 문자나 숫자등도 리셋된다.
초기화되는 것으로 문제가 발생하는 경우는 keep-alive 태그로 감싸주면 컴포넌트의 상태를 유지하면서 변환되도록 핤 ㅜ있다.
<!--App.vue-->
<template>
<div>
〜생략〜
<keep-alive>
<component :is="componentName"></component>
</keep-alive>
</div>
</template>
참고자료
'IT > 언어' 카테고리의 다른 글
[Vue.js] export default에 대해서 (0) | 2023.01.16 |
---|---|
[Vue.js] watch 오브젝트 (handler 유무, deep, immediate등) (0) | 2023.01.15 |
[Vue.js] v-model 이란? (0) | 2023.01.13 |
[Vue.js] Vue.js에서의 deep selector 그리고 작성법 (0) | 2023.01.13 |
[Vue.js] Atomic Design 베이스의 Vue 컴포넌트 설계 (0) | 2023.01.10 |