Skip to content

Vue-property-decorator

Vue.js and Property Decorator.

This library fully depends on vue-class-component.

기본

구성 요소(기능) 정의

@Component는 정의된 클래스를 Vue가 인식할 수 있는 형식으로 변환합니다. 아래 2개는 같은 의미입니다.

export default {
  name: 'SampleComponent'
};
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {}

이때 vue-property-decorator의 Vue 클래스 상속을 잊지 않도록 조심하세요.

Data

Data는 클래스 멤버로 정의하여 사용할 수 있습니다. 다음 예제에선 이름과 나이를 Data가 갖고 있습니다.

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;
}

Data를 템플릿에서 사용할 때는 플레인 Vue 처럼 참조할 수 있습니다.

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

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 처럼 참조할 수 있습니다.

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

Methods

메소드는 클래스의 메소드로 정의하면 사용할 수 있습니다. 다음 예제에서는 버튼을 누를 때 onClickButton 메서드를 호출합니다.

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

이런 템플릿이 있을 때 onClickButton는 다음과 같이 정의할 수 있습니다.

export deafult {
  methods: {
    onClickButton() {
      // 버튼 눌렸을 때 처리
    }
  }
};
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class SampleComponent extends Vue {
  onClickButton() {
    // 버튼 눌렸을 때 처리
  }
}

React 처럼 메소드에서 this 바인딩할 필요가 없습니다.

라이프 사이클 훅

라이프 사이클 훅은 클래스 수명 주기(라이프 사이클)의 이름으로 메소드를 정의하면 사용할 수 있습니다.

export default {
  mounted() {
    // 컴포넌트가 마운트 되었을 때 처리
  },
  beforeDestroy() {
    // 컴포넌트가 삭제되기 직전 처리
  }
}
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에서 손자 컴포넌트로 값을 전달하는 등 매우 간편하게 활용할 수 있습니다.

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

@Emit

사용시 주의사항

데코레이션 사용시 @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);
  }
}

  • 부모 컴포넌트

<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);
  }
}

@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;
}

이 때 암묵적으로 valueProp으로 정의되므로 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

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
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

@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],
    },
  },
}

See also

Favorite site