Vue-property-decorator
Vue.js and Property Decorator.
This library fully depends on vue-class-component.
기본
구성 요소(기능) 정의
@Component는 정의된 클래스를 Vue가 인식할 수 있는 형식으로 변환합니다. 아래 2개는 같은 의미입니다.
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {}
이때 vue-property-decorator의 Vue 클래스 상속을 잊지 않도록 조심하세요.
Data
Data는 클래스 멤버로 정의하여 사용할 수 있습니다. 다음 예제에선 이름과 나이를 Data가 갖고 있습니다.
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
name = 'simochee';
age = 21;
}
Data를 템플릿에서 사용할 때는 플레인 Vue 처럼 참조할 수 있습니다.
Computed
계산 속성(Computed)은 클래스 Getter로 정의하여 사용할 수 있습니다. 다음 예제는 Data에 정의된 점수를 3배로 계산하는 속성을 정의합니다.
export default {
data() {
return {
score: 55
}
},
computed: {
triple() {
return this.score * 3;
}
}
};
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
score = 55;
get triple() {
return this.score * 3;
}
}
Computed를 템플릿에서 사용할 때는 플레인 Vue 처럼 참조할 수 있습니다.
Methods
메소드는 클래스의 메소드로 정의하면 사용할 수 있습니다. 다음 예제에서는 버튼을 누를 때 onClickButton 메서드를 호출합니다.
이런 템플릿이 있을 때 onClickButton
는 다음과 같이 정의할 수 있습니다.
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
onClickButton() {
// 버튼 눌렸을 때 처리
}
}
React 처럼 메소드에서 this 바인딩할 필요가 없습니다.
라이프 사이클 훅
라이프 사이클 훅은 클래스 수명 주기(라이프 사이클)의 이름으로 메소드를 정의하면 사용할 수 있습니다.
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
mounted() {
// 컴포넌트 마운트되었을 때 처리
}
beforeDestroy() {
// 컴포넌트 삭제되기 직전 처리
}
}
vue-property-decorator는 라이프 사이클 훅과 메소드가 같은 공간에서 정의되므로 라이프 사이클의 이름으로 메소드를 정의하지 않도록 주의하세요.
@Component
@Component 인수로 Vue 객체를 지정할 수 있습니다.
이후 각종 데코레이터를 소개합니다만, 거기서 정의할 수 없습니다. components, filters, mixins 같은 속성은 @Component 인수로 지정합니다.
export deafult {
components: {
AppButton,
ProductList
},
directives: {
resize
},
filters: {
dateFormat
},
mixins: [
PageMixin
]
};
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
AppButton,
ProductList
},
directives: {
resize
},
filters: {
dateFormat
},
mixins: [
PageMixin
]
})
export default class SampleComponent extends Vue {
}
그 밖에도 아래와 같은 속성을 지정할 수 있습니다.
- 옵션/데이터
- 옵션/DOM
- 옵션/라이프 사이클 후크
- 옵션/asset
- 옵션/구성
- 옵션/기타
@Prop
@Prop은 정의한 멤버들을 props로 사용할 수 있도록 지원합니다. 부모 컴포넌트에서 정의한 멤버 이름을 props로 지정합니다.
export deafult {
props: {
userName: {
type: String,
required: true
},
isVisible: {
type: Boolean,
default: false
}
}
};
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
@Prop({ type: String, required: true })
userName: string;
@Prop({ type: Boolean, defualt: false })
isVisible: boolean;
}
@Watch
@Watch
는 첫번째 인수로 모니터링(감시)할 값의 경로, 두번째 인수는 왓쳐의 옵션으로 지정합니다. 다음 예제는 하나의 Data와 Object 속성 값을 모니터링(감시)하는 방법입니다. 또한, immediate: true
는 컴포넌트 초기화 시에도 실행할지를 지정하는 옵션입니다.
export deafult {
data() {
isLoading: false,
profile: {
name: 'simochee',
age: 21
}
},
watch: {
isLoading() {
// 로딩 상태가 바뀌었을 때의 처리
},
'profile.age': {
handler: function() {
// 프로필의 나이가 변경되었을 때의 처리
},
immediate: true
}
}
};
import { Component, Watch, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
isLoading = false;
profile = {
name: 'simochee',
age: 21
};
@Watch('isLoading')
onChangeLoadingStatus() {
// 로딩 상태가 바뀌었을 때의 처리
}
@Watch('profile.age', { immediate: true })
onChangeProfileAge() {
// 프로필의 나이가 변경되었을 때의 처리
}
}
Vue 사양에서도 알 수 있듯이 @Watch는 동일한 경로를 여러번 지정할 수 없습니다. 여러번 지정할 경우 앞선 정의는 사라지기 때문입니다. (뒤에 정의한 내용만 존재함)
응용
@SyncProp
Vue.js는 props를 지정할 때 .sync
수식자를 부여하여 자식 컴포넌트에서 부모 컴포넌트의 값을 변경할 수 있습니다.
@update:
<Prop 이름>라는 이벤트를 받으면 Data에 대입하는 처리를 암시적으로 실시합니다.
- 부모 컴포넌트
<template>
<!-- 아래 2개는 같은 의미 -->
<ChildComponent
:childValue.sync="value"
/>
<ChildComponent
:childValue="value"
@update:childValue="value = $event"
/>
</template>
이때, 자식 컴퍼넌트 이후 .sync
프로퍼티를 릴레이(전달)할 때 편리한 것이 @PropSync
데코레이터입니다. 이 데코레이터를 사용하지 않는다면 아래와 같이 코딩해야 합니다.
- 자식 컴포넌트
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
@Prop({ type: String })
childValue: string;
// value를 변경하고 싶을때 호출
updateValue(newValue) {
this.$emit('update:childValue', newValue);
}
}
이때, @PropSync
로 정의하면 멤버에 값을 할당하는 것만으로 동일한 처리가 가능합니다.
import { Component, PropSync, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
@PropSync({ type: String })
childValue: string;
// value를 변경하고 싶을 때 호출
updateValue(newValue) {
this.childValue = newValue;
}
}
대입하면 값의 변경을 알릴 수 있고, .sync에서 손자 컴포넌트로 값을 전달하는 등 매우 간편하게 활용할 수 있습니다.
@Emit
사용시 주의사항 |
데코레이션 사용시 |
Vue에서는 컴포넌트끼리 값을 양방향으로 주고받을 수 있습니다. 부모에서 자식으로 값 전달할 땐 Prop을 지정하고, 자식에서 부모로 값 전달할 땐 이벤트를 호출해 액션, 값을 전달합니다. 이 때 자식에서 부모로 값을 전달할 때 실행하는 이벤트가 $emit 입니다. 다음 예제에서는 자식 컴포넌트와 부모 컴포넌트가 데이터를 주고 받습니다. submit 이벤트로 부모 컴포넌트가 받았음을 통지합니다.
- 자식 컴포넌트
<template>
<form @submit="onSubmit">
<input v-model="value">
<button type="submit">Submit</button>
</submit>
</template>
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class ChildComponent extends Vue {
value = '';
// 값 보내기 처리
onSubmit() {
this.$emit('submit', this.value);
}
}
- 부모 컴포넌트
import { Component, Vue } from 'vue-property-decorator';
import ChildComponent from './ChildComponent.vue';
@Component({
components: {
ChildComponent
}
})
export default class ParentComponent extends Vue {
async onReceiveSubmit(newValue: string) {
// $emit에서 2번째 인자를 받을 수 있도록 처리
await this.$request.post(newValue);
}
}
@Emit
에서는 \(emit</span> 처리를 미리 정의할 수 있습니다. 이벤트 이름은 @Emit 첫번째 인수에 명시적으로 지정했거나 생략한 경우 앞에서 정의한 메소드 이름을 사용합니다. 또한, 메소드에서 값을 리턴할 때 <span markdown="1">\)emit에서 그 값을 보내게 됩니다. 위 예제의 자식 컴퍼넌트를 @Emit
으로 다시 작성하면 다음과 같습니다.
<template>
<form @submit="submit">
<input v-model="value">
<button type="submit">Submit</button>
</submit>
</template>
import { Component, Emit, Vue } from 'vue-property-decorator';
@Component
export default class ChildComponent extends Vue {
value = '';
// 값 보내기 처리
// 이벤트 이름을 지정하지 않는 경우에도 ()는 생략할 수 없음
@Emit()
submit() {
return this.value;
}
}
이밖에도 @Emit
비동기 메소드를 설정할 수 있습니다. 또한 카멜 케이스로 이벤트 이름, 메소드 이름을 지정한 경우 부모 컴포넌트에서 받을 때 케밥 케이스로 변환되므로 주의가 필요합니다.
// 자식 컴포넌트
@Emit()
submitForm() {}
// 부모 컴포넌트
<ChildComponent
@submit-form="onSubmit"
@submitForm"onSubmit" // 활성화
/>
@Ref
@Ref
는 $refs에서 참조할 수 있는 요소, 컴포넌트 형식을 정의합니다. 사전에 정의해 둠으로써 오타 및 수정에 대응하기 쉬워집니다.
<template>
<ChildComponent ref="childComponent" />
<button ref="submitButton">Submit</button>
</template>
import { Component, Vue } from 'vue-property-decorator';
import ChildComponent from '@/component/ChildComponent.vue';
@Component({
components: {
ChildComponent
}
});
export default class SampleComponent extends Vue {
@Ref() childComponent: ChildComponent;
@Ref() submitButton: HTMLButtonElement;
mounted() {
// 자식 요소의 메소드 실행
this.childComponent.updateValue();
// 버튼에 포커스
this.submitButton.focus();
}
}
상급
이후는 상급자를 위한 것이라 설명이 자세하지 않습니다. 필요하다면 공식 문서를 참조하세요.
@Model
Vue의 Model을 정의합니다. Vue에서는 Model을 지정할 때 Prop을 정의하고 거기에 여러 정보 등을 기재하지 않았지만, 데코레이터는 @Model
에 함께 정의할 수 있습니다. 다음의 2개는 같은 의미입니다.
export deafult {
props: {
value: {
type: String,
required: true
}
},
model: {
prop: 'value',
event: 'update'
}
};
import { Component, Model, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
@Model('update', { type: String, required: true })
value: string;
}
이 때 암묵적으로 value
가 Prop
으로 정의되므로 value
라는 data
나 버튼 methods
를 정의할 수 없습니다.
Example
Checkbox.vue
(컴포넌트)를 만들고 아래와 같이 코딩합니다.
<template>
<input type="checkbox" :checked="checked" @change="change">
</template>
<script lang="ts">
import {Vue, Component, Model, Emit} from 'vue-property-decorator';
@Component
export default class MyCheckbox extends Vue {
@Model('change', {type: Boolean}) readonly checked!: boolean;
@Emit()
change(event: Event) {
return event.target.checked;
}
}
</script>
src/component/Home.vue
(사용하는 측):
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<my-checkbox v-model="checked" @change="change"></my-checkbox>
{{ text }}
</div>
</template>
<script lang="ts">
import { Vue, Component, Provide } from 'vue-property-decorator';
import MyCheckbox from '@/components/Checkbox.vue';
@Component({
components: {
MyCheckbox,
},
})
export default class Home extends Vue {
checked: boolean = false;
text: string = '선택함';
change(checked: boolean) {
this.checked = checked;
this.text = checked ? '선택함' : '선택하지 않음';
}
}
</script>
@VModel
- v-model 항목 참조.
import { Vue, Component, VModel } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@VModel({ type: String }) name!: string
}
is equivalent to
export default {
props: {
value: {
type: String,
},
},
computed: {
name: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
},
},
},
}
@Provide / @Inject
Vue에서는 부모 provide로 정의된 값을 하위 요소(부모와 자식이 아니어도 좋다)에서 inject로 참조할 수 있습니다.
다음의 2개는 같은 의미입니다.
-
Parent.vue
-
Child.vue
export deafult {
inject: {
foo: 'foo',
bar: 'bar',
optional: { from: 'optional', default: 'default' }
}
};
-
Parent.vue
import { Component, Provide, Vue } from 'vue-property-decorator';
@Component
export default class ParentComponent extends Vue {
@Provide() foo = 'foo';
@Provide('bar') baz = 'bar';
}
-
Child.vue
import { Component, Inject, Vue } from 'vue-property-decorator';
@Component
export default class ChildComponent extends Vue {
@Inject() foo: string;
@Inject('bar') bar: string;
@Inject({ from: 'optional', default: 'default' }) optional: string;
@Inject(symbol) baz: string;
}
@ProvideReactive / @ProvideInject
@Provide
/@Inject
확장입니다. 부모 컴포넌트에서 @ProvideReactive
로 제공된 값이 변경되면 자식 컴포넌트에서 알 수 있습니다.
-
Parent.vue
import { Component, ProvideReactive, Vue } from 'vue-property-decorator';
@Component
export default class ParentComponent extends Vue {
@ProvideReactive() foo = 'foo';
}
-
Child.vue
import { Component, InjectReactive, Vue } from 'vue-property-decorator';
@Component
export default class ChildComponent extends Vue {
@InjectReactive() foo: string;
}
readonly와 !, ? 에 대해서
vue-property-decorator 샘플 코드에 readonly
, 버튼 prop!: String
과 같은 !
가 등장합니다. 모두 TypeScript 기능입니다.
readonly
한정자는 멤버 변수를 쓰기 전용으로 선언하는 것입니다. (사용하기 위한 용도)
Vue에선 Prop
, Model
에 직접 할당하면 오류입니다.
잘못된 할당을 사전에 방지하기 위해 @Prop
, @Model
에서 정의한 멤버 변수는 readonly
한정자로 선언하는 것을 추천합니다.
import { Component, Prop, PropSync, Watch, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
@Prop({ type: String }) readonly name: string;
@Model('update', { type: Object }) readonly profile: IProfile;
@PropSync({ type: String }) value: string; // 대입 가능
}
또한, 데코레이터에 정의된 모든 멤버 변수에 붙어있는 !
가 NonNullAssertion 오퍼레이터라는 기능입니다. !
가 붙은 속성이 Null
/ Undefined
가 아님을 명시합니다.
그러나 !
는 required: true
또는 기본값이 설정된 속성만 지정하는 것을 추천합니다.
반대로, 필수 항목이 아니며 기본값도 지정되지 않은 경우 ?
를 추천합니다.
이 ?
속성이 임의 항목이며, undefined
가능성이 있음을 명시합니다.
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
@Prop({ type: String, required: true })
readonly name!: string;
@Prop({ type: Array, default: () => [] })
readonly items!: string[];
@Prop({ type: Object });
readonly profile?: IProfile;
mounted() {
// undefined 가능성이 있는 객체 속성을
// 참조할 경우 오류가 발생한다
profile.age;
}
}
보다 안전하게 구현하고 싶다면, 조심스럽게 개발하세요.
Example
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@Prop(Number) readonly propA: number | undefined
@Prop({ default: 'default value' }) readonly propB!: string
@Prop([String, Boolean]) readonly propC: string | boolean | undefined
}
is equivalent to
export default {
props: {
propA: {
type: Number,
},
propB: {
default: 'default value',
},
propC: {
type: [String, Boolean],
},
},
}