Skip to content
Search

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.

export default {
  name: 'SampleComponent'
};
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.

export default {
  data() {
    return {
      name: 'simochee',
      age: 21
    }
  }
};
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.

<template>
  <!-- simochee (21) -->
  <p>{{name}} ({{age}})</p>
</template>

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.

<template>
  <!-- Triple score: 163! -->
  <p>Triple score: {{triple}}!</p>
</template>

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.

<template>
  <button @click="onClickButton">Click Me!</button>
</template>

Given a template like this, onClickButton can be defined as follows.

export deafult {
  methods: {
    onClickButton() {
      // 버튼 눌렸을 때 처리
    }
  }
};
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.

export default {
  mounted() {
    // 컴포넌트가 마운트 되었을 때 처리
  },
  beforeDestroy() {
    // 컴포넌트가 삭제되기 직전 처리
  }
}
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.

<template>
  <SunComponent
    :sunValue.sync="childValue"
  />
</template>

@Emit

Caveats when using

When using the decorator, writing just @Emit does not propagate the event

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

<template>
  <ChildComponent
    @submit="onReceiveSubmit"
  />
</template>
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

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
export deafult {
  provide: {
    foo: 'foo',
    bar: 'bar'
  }
};
  • 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

author Zer0 sitemap