vue-property-decorator 에 대해 알아봅시다.

vue-property-decorator 는 vuejs 에서 typescript로 개발할 때, 클래스 컴포넌트 스타일로 개발하기 쉽게 도와주는 데코레이터 들입니다. 제공해 주는 데코레이터들로는

  • @Emit
  • @Inject
  • @Model
  • @Prop
  • @Provide
  • @Watch
  • @Component

가 있습니다. 이 중 @Component 데코레이터와 @Prop 데코레이터는 지난 포스팅에서 살펴 봤습니다.

그럼 우리가 Vue와 typescript를 사용하면서 제일 많이 사용하게 될 @Prop 데코레이터부터 살펴보도록 하겠습니다.

@Prop()

1
@Prop(options: (PropOptions | Constructor[] | Constructor) = {}) decorator

출처: https://github.com/kaorun343/vue-property-decorator

위와 같은 형식으로 사용하며,

1
2
3
4
5
6
7
8
import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
@Prop(Number) propA!: number
@Prop({ default: 'default value' }) propB!: string
@Prop([String, Boolean]) propC!: string | boolean
}

이렇게 만들어진 코드는 아래 코드와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
props: {
propA: {
type: Number
},
propB: {
default: 'default value'
},
propC: {
type: [String, Boolean]
},
}
}

default 옵션을 통해 기본값을 지정할 수도 있습니다.

@Emit()

Emit 데코레이터는 자식 -> 부모 컴포넌트로 이벤트 바인딩을 할때 주로 사용합니다.

1
@Emit(event?: string)

형식으로 선언되며, Emit 하고자 하는 event 명을 string 형식으로 넣어 선언하면 됩니다.

1
2
@Emit('change')
private onChange(value: string) {}

과 같이 선언하며, ‘change’와같이 emit되는 이벤트 명을 넣지 않을 경우, 함수 명을 이벤트 명으로 사용하게 됩니다. (단, 이벤트명을 넣지 않아 자동으로 이벤트 emit이 선언된 경우에는 카멜 케이스의 함수명이 케밥 케이스의 함수명으로 변경되어 적용됩니다. 예: onChange -> on-change)

우리가 생성했던 test-project에 Emit을 사용한 코드를 넣어 봅시다.

Home.vue파일의 내용을 아래와 같이 바꿉니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld
msg="Welcome to Your Vue.js + TypeScript App"
@onClick="clickEventHandler"
/>
</div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src

@Component({
components: {
HelloWorld,
},
})
export default class Home extends Vue {

private clickEventHandler() {
console.log('click!');
}

}
</script>

HelloWorld에 onClick 이라는 이름으로 clickEventHandler 함수를 바인딩 했습니다.
그리고 클래스에 clickEventHandler() 라는 함수를 정의 했습니다. 해당 함수가 실행 되면 콘솔 창에 click!이라는 메세지를 출력합니다.

HelloWorld.vue 파일을 열어 내용을 아래와 같이 바꿉니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button @click="onClick" >클릭!</button>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;

@Emit('onClick')
private onClick() {
//
}
}
</script>

.hello 클래스를 가진 div에 클릭을 할 경우 onClick함수를 실행시키는 버튼을 추가 했습니다.
그리고 onClick() 함수 앞에 Emit(‘onClick’) 이라고 Emit 데코레이터를 선언 했습니다. 이제 이 onClick() 함수가 실행되면 부모 클래스로 onClick 이벤트를 Emit 합니다.

이 Emit 데코레이터는 @Model 데코레이터와 함께 많이 사용되며, 이벤트 바인딩을 할때 필수기 때문에 잘 봐두어야 합니다.

만약, event emitting과 함께 데이터를 전달하고 싶다면??

Home.vue와 HelloWorld.vue 클래스를 아래와 같이 바꿔봅시다.

Home.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld
msg="Welcome to Your Vue.js + TypeScript App"
@onClick="clickEventHandler"
/>
</div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src

@Component({
components: {
HelloWorld,
},
})
export default class Home extends Vue {

private clickEventHandler(message: string) {
console.log(message);
}

}
</script>

HelloWorld.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button @click="(event) => onClick('클릭이벤트가 발생했했습니다.')" >클릭!</button>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;

@Emit('onClick')
private onClick(message: string) {
//
}
}
</script>

선언하는 함수의 args를 전달하고 싶은 데이터 형식으로 넣어 전달하면 됩니다.

@Model()

Model 데코레이터는 @Prop 데코레이터 다음으로 많이 쓰이게 될 데코레이터가 아닌가 합니다. Prop은 일반적으로 단방향 바인딩인데 반해, Model은 양방향 바인딩을 제공합니다.

1
@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {}) decorator

선언방법은 위와 같습니다.

1
2
3
4
5
6
import { Vue, Component, Model } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
@Model('change', { type: Boolean }) checked!: boolean
}

위 코드는 아래와 같은 코드와 동일한 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
export default {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: {
type: Boolean
},
},
}

이제 우리의 test project에 코드를 만들어봅시다.

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="home">
{{count}}
<HelloWorld v-model="count"/>
</div>
</template>

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src

@Component({
components: {
HelloWorld,
},
})
export default class Home extends Vue {

private count: number = 0;

}
</script>

count 변수를 새로 넣었고, 이를 화면에 출력하도록 했습니다.
그리고 HelloWorld 컴포넌트에는 count 변수를 v-model로 양방향 바인딩을 했습니다.

HelloWorld.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="hello">
<button @click="(event) => onChange(count+1)" >add</button>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit, Model } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {

@Model('change', {type: Number})
private count!: number;

@Emit('change')
private onChange(count: number) {}

}
</script>

HelloWorld 컴포넌트의 다른 내용은 지우고 버튼 하나만 남겨뒀습니다.
count 변수를 model로 받도록 @Model 데코레이터와 함께 선언했고, 버튼을 눌렀을 경우 받은 count에 +1을 해서 change event를 emit 하도록 선언 했습니다.

이제 컴파일 하고 버튼을 클릭 하면, Home.vue 컴포넌트에서 count값이 1씩 올라가는 것을 확인할 수 있습니다.

@Watch

@Watch 데코레이터는 값이 변경되는지를 감시하고 있다가, 변경 됐을 때 같이 선언한 함수를 실행시켜주는 데코레이터 입니다.

1
2
3
4
@Watch('count')
onChangeCount(val: number, oldVal: number) {
//함수 내용
}

우리의 Home.vue에 count가 변경될 때 log를 찍어주는 내용을 추가해 봅시다.

1
2
3
4
@Watch('count')
private onChangeCount(val: number, oldVal: number) {
console.log(`${oldVal} -> ${val}`);
}

를 Home.vue에 추가해 주고 확인해 봅니다.

클릭할때마다 count 의 값이 변경되고, 이를 감지해 onChangeCount가 실행되는 것을 확인 할 수 있습니다.

이 외 데코레이터(@Inject / @Provide)

Inject와 Provide는 서비스 주입 형태 사용시 사용되게 되는데, 개인적인 의견으로는 vuex 패턴으로 프로젝트를 디자인 하게 될 경우, 사용빈도가 낮아지게 되어 별도로 설명하지는 않겠습니다.

(vue 공식 페이지에서도 provide와 inject는 주로 고급 플러그인/컴포넌트 라이브러리를 위해 제공되고, 일반 애플리케이션 코드에서는 사용하지 않는 것이 좋다고 설명되어 있습니다.)

자세한 설명

다음 포스트에서는 vuex를 typescript 클래스 형태로 사용하는 법에 대해서 작성하도록 하겠습니다.

감사합니다.