Vue-property-decorator
lastmod 2021-07-23created 2020-09-2513 edits
Vue.js and Property Decorator.
This library fully depends on vue-class-component.
Basics
Defining a component (feature)
@Component converts the defined class into a format Vue can recognize. The two below mean the same thing.
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {}
Be careful not to forget to inherit the Vue class from vue-property-decorator.
Data
Data can be defined and used as class members. In the following example, Data holds a name and an age.
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
name = 'simochee';
age = 21;
}
When using Data in a template, you can reference it just like in plain Vue.
Computed
Computed properties can be defined and used as class getters. The following example defines a property that triples the score defined in Data.
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;
}
}
When using Computed in a template, you can reference it just like in plain Vue.
Methods
Methods can be used by defining them as class methods. In the following example, the onClickButton method is called when the button is pressed.
Given a template like this, onClickButton can be defined as follows.
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
onClickButton() {
// 버튼 눌렸을 때 처리
}
}
Unlike React, there is no need to bind this in methods.
Lifecycle hooks
Lifecycle hooks can be used by defining methods named after the class lifecycle stages.
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
mounted() {
// 컴포넌트 마운트되었을 때 처리
}
beforeDestroy() {
// 컴포넌트 삭제되기 직전 처리
}
}
In vue-property-decorator, lifecycle hooks and methods are defined in the same space, so be careful not to define a method with a lifecycle name.
@Component
You can pass a Vue object as the @Component argument.
Various decorators are introduced below, but these cannot be defined there. Properties such as components, filters, and mixins are specified as @Component arguments.
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 {
}
You can also specify the following properties.
- Options/Data
- Options/DOM
- Options/Lifecycle Hooks
- Options/asset
- Options/Composition
- Options/Misc
@Prop
@Prop lets the defined members be used as props. The member names defined in the parent component are specified as 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 takes the path of the value to monitor (watch) as the first argument and the watcher options as the second argument. The following example shows how to monitor (watch) a single Data value and an Object property value. Also, immediate: true is an option that specifies whether to also run when the component is initialized.
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() {
// 프로필의 나이가 변경되었을 때의 처리
}
}
As the Vue specification suggests, @Watch cannot specify the same path more than once. If you specify it multiple times, the earlier definitions are lost. (Only the last definition remains)
Applied usage
@SyncProp
In Vue.js, adding the .sync modifier when specifying props lets a child component change the parent component's value.
On receiving an event named @update:<Prop name>, it implicitly assigns the value to Data.
- Parent component
<template>
<!-- 아래 2개는 같은 의미 -->
<ChildComponent
:childValue.sync="value"
/>
<ChildComponent
:childValue="value"
@update:childValue="value = $event"
/>
</template>
The @PropSync decorator is handy for relaying (passing on) a .sync property beyond the child component. Without this decorator, you would have to write code like this.
- Child component
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);
}
}
If you define it with @PropSync instead, simply assigning a value to the member does the same thing.
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;
}
}
Assigning notifies the value change, so it is very easy to use, for example to pass a value to a grandchild component with .sync.
@Emit
| Caveats when using |
| When using the decorator, writing just |
In Vue, components can pass values to each other in both directions. To pass a value from parent to child, you specify a Prop; to pass a value from child to parent, you call an event to pass the action and value. The event fired when passing a value from child to parent is $emit. In the following example, the child component and parent component exchange data. The submit event notifies that the parent component has received it.
- Child component
<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);
}
}
- Parent component
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);
}
}
With @Emit, you can predefine the \(emit</span> handling. The event name is the one explicitly given as the first argument of @Emit, or, if omitted, the name of the method defined after it. Also, when the method returns a value, <span markdown="1">\)emit sends that value. Rewriting the child component from the example above with @Emit gives the following.
<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;
}
}
You can also set an async method with @Emit. Note also that if you specify the event name or method name in camelCase, it is converted to kebab-case when received in the parent component.
// 자식 컴포넌트
@Emit()
submitForm() {}
// 부모 컴포넌트
<ChildComponent
@submit-form="onSubmit"
@submitForm"onSubmit" // 활성화
/>
@Ref
@Ref defines the types of elements and components that can be referenced from $refs. Defining them in advance makes it easier to deal with typos and changes.
<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();
}
}
Advanced
The following is for advanced users, so the explanations are not detailed. Refer to the official documentation if needed.
@Model
Defines a Vue Model. In Vue, when specifying a Model you define a Prop and did not write various information there, but with the decorator it can be defined together in @Model. The following two mean the same thing.
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;
}
Here value is implicitly defined as a Prop, so you cannot define data or button methods named value.
Example
Create Checkbox.vue (a component) and write the following code.
<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 (the consuming side):
<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
- See 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
In Vue, a value defined with provide in a parent can be referenced with inject in descendant elements (they do not have to be a direct parent and child).
The following two mean the same thing.
-
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
An extension of @Provide/@Inject. When a value provided with @ProvideReactive in the parent component changes, the child component can detect it.
-
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;
}
About readonly, !, and ?
The vue-property-decorator sample code contains readonly and ! as in button prop!: String. Both are TypeScript features.
The readonly modifier declares a member variable as write-only. (For use only)
In Vue, assigning directly to a Prop or Model is an error.
To prevent invalid assignments in advance, it is recommended to declare member variables defined with @Prop and @Model with the readonly modifier.
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; // 대입 가능
}
Also, the ! attached to all member variables defined with decorators is a feature called the NonNullAssertion operator. It states explicitly that the property marked with ! is not Null/Undefined.
However, it is recommended to use ! only on properties with required: true or a default value.
Conversely, if a property is not required and has no default value, ? is recommended.
This ? states explicitly that the property is optional and may be 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;
}
}
If you want a safer implementation, develop carefully.
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],
},
},
}
See also
Favorite site
- Github - vue-property-decorator
- [Recommended] vue-property-decorator summary :: ( MSFL - Knowledge Repository )
- Vue Property Decorator | Cracking Vue.js
- Writing Class-Based Components with Vue.js and TypeScript | DigitalOcean
- [Recommended] First steps with vue-property-decorator (vue js nuxt support)