Vue Property Decorator
vue-property-decorator is a library that provides a set of TypeScript decorators for creating Vue components with a class-based syntax, building on top of `vue-class-component`. It allows developers to define props, methods, computed properties, watchers, and lifecycle hooks using declarative decorators rather than the traditional Vue options API. This approach aims to improve readability and maintainability, especially in larger TypeScript projects. The current stable version is 9.1.2. While its release cadence has varied, it remains actively maintained, though primarily for Vue 2 applications. Its key differentiator is simplifying common Vue patterns into concise, type-safe decorator declarations, providing a more object-oriented way to define components compared to the standard options API or the newer Vue 3 Composition API. It is largely considered a bridge for those who prefer class-based component development within the Vue 2 ecosystem.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'prototype')
cause This often occurs due to a mismatch in `vue-class-component` versions, an incorrect import of `@Component`, or decorators not being properly enabled.fixVerify that `vue-class-component` is compatible with your `vue-property-decorator` version (e.g., `vue-class-component@^7.0.0` for `vue-property-decorator@8.x.x+`). Ensure `import Component from 'vue-class-component'` is used correctly and that TypeScript decorators are enabled in `tsconfig.json`. -
Error: Decorators are not enabled.
cause Your TypeScript configuration is missing the required flags to enable decorator syntax.fixAdd or update your `tsconfig.json` under `compilerOptions` with: `"experimentalDecorators": true, "emitDecoratorMetadata": true`. -
Cannot find module 'vue-property-decorator' or its corresponding type declarations.
cause The package is either not installed, or your import path is incorrect, or TypeScript cannot resolve its types.fixRun `npm install vue-property-decorator vue-class-component vue` or `yarn add vue-property-decorator vue-class-component vue` to ensure all peer dependencies are installed, and check your import statements for typos.
Warnings
- breaking Major version 8.0.0 introduced a breaking change requiring `vue-class-component` version 7.x.x. Upgrading `vue-property-decorator` to v8+ without simultaneously upgrading `vue-class-component` will lead to runtime errors.
- gotcha `vue-property-decorator` is designed exclusively for Vue 2 applications. It is not compatible with Vue 3's Composition API, `script setup`, or its updated reactivity system.
- gotcha TypeScript decorators, which `vue-property-decorator` relies on, require specific compiler options (`experimentalDecorators` and `emitDecoratorMetadata`) to be enabled in your `tsconfig.json`.
- deprecated Vue 2, the core framework that `vue-property-decorator` targets, reached End of Life (EOL) on December 31, 2023. While `vue-property-decorator` itself is actively maintained, the underlying Vue 2 platform is no longer receiving official support or updates from the Vue team.
Install
-
npm install vue-property-decorator -
yarn add vue-property-decorator -
pnpm add vue-property-decorator
Imports
- Component
import { Component } from 'vue-class-component'import Component from 'vue-class-component'
- Prop
import Prop from 'vue-property-decorator'
import { Prop } from 'vue-property-decorator' - Emit
const { Emit } = require('vue-property-decorator')import { Emit } from 'vue-property-decorator' - Watch
import { Watch } from 'vue-property-decorator' - Vue
import { Vue } from 'vue'import Vue from 'vue'
- Inject, Provide
import { Inject, Provide } from 'vue-property-decorator'
Quickstart
<!-- MyButton.vue -->
<template>
<button :disabled="disabled" @click="onClick">
{{ formattedLabel }} ({{ internalClickCount }})
</button>
</template>
<script lang="ts">
import { Component, Prop, Emit, Watch, Vue } from 'vue-property-decorator';
@Component
export default class MyButton extends Vue {
@Prop({ type: String, default: 'Click Me' })
readonly label!: string;
@Prop({ type: Boolean, default: false })
readonly disabled!: boolean;
private internalClickCount: number = 0;
get formattedLabel(): string {
return this.label.toUpperCase();
}
@Emit('button-clicked')
onClick(event: MouseEvent): number {
this.internalClickCount++;
console.log(`Button clicked! New count: ${this.internalClickCount}`);
return this.internalClickCount;
}
@Watch('internalClickCount')
onInternalClickCountChange(newValue: number, oldValue: number) {
console.log(`Click count changed from ${oldValue} to ${newValue}`);
}
mounted() {
console.log('MyButton mounted!');
}
}
</script>
<style scoped>
button {
padding: 10px 20px;
background-color: #42b983;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
</style>