Binding helpers for Vuex and vue-class-component on steroids
$ npm install --save types-vue
# or
$ yarn add types-vue
There are these decorators for a component:
- Component (from 'vue-class-component')
- Prop
- Watch
- MapGetter
- MapAction
And these decorators for a vuex module:
- Module
- Getter
- Mutation
- Action
This decorator adds the field to "props" component collection. It can be invoked in different ways:
- Without parameters
- With the type constructor (ex. Number, String, Boolean...)
- With PropOptions:
- type: type constructor (ex. Number, String, Boolean...)
- required: boolean
- default: defaul value of the prop
- validator: a validator function
import { Vue, Component, Prop } from 'types-vue';
@Component
export default class MyComponent extends Vue {
@Prop()
title!: string;
@Prop(Number)
count!: number;
@Prop({
type: String,
required: false,
default: '[email protected]',
validator(value: string): boolean {
return true;
}
})
email!: string;
}
This is like:
export default {
props: {
title: { },
count: Number,
email: {
type: String,
required: false,
default: '[email protected]',
validator(value: string): boolean {
return true;
}
},
}
}
This decorator adds the method to "watch" component collection. It can be invoked in different ways:
- With the name of the property to watch.
- With the name of the property to watch and WatchOptions:
- deep: boolean
- inmediate: boolean
import { Vue, Component, Watch } from 'types-vue';
@Component
export default class MyComponent extends Vue {
title: string = 'hello';
counter: number = 0;
@Watch('title')
onTitleChanged(value: string): void {
console.log('title is ' + value);
}
@Watch('counter', { deep: true, immediate: true })
onCounterChanged(value: number, oldValue: number): void {
console.log('counter is ' + value.toString());
}
}
This is like:
export default {
data() {
return {
title: 'hello',
counter: 0
};
}
watch: {
'title': {
handler: 'onTitleChanged',
immediate: false,
deep: false
},
'counter': {
handler: 'onCounterChanged',
immediate: true,
deep: true
}
},
methods: {
onTitleChanged(value) {
console.log('title is ' + value);
},
onCounterChanged(value, oldValue) {
console.log('counter is ' + value.toString());
}
}
}
}
This decorator adds the method to "filters" component collection.
import { Vue, Component, Watch } from 'types-vue';
@Component
export default class MyComponent extends Vue {
@Filter()
static withExclamation(value: string): string {
return '!' + value;
}
}
This is like:
export default {
filters: {
withExclamation(value) {
return '!' + value;
}
}
}
}
When you are using Vuex, this decorator adds the field to "computed" as a vuex mapGetter in the component. It can be invoked in different ways:
- Without parameters
- With MapGetterOptions:
- namespace: the name of the module namespace
import { Vue, Component, MapGetter } from 'types-vue';
@Component
export default class MyComponent extends Vue {
@MapGetter()
title: string;
@MapGetter({ namespace: 'counter' })
count: number;
}
This is like:
export default {
computed: {
...mapGetters(['title']),
...mapGetters('counter', ['count'])
}
}
When you are using Vuex, this decorator adds the field to "methods" as a vuex mapAction in the component. It can be invoked in different ways:
- Without parameters
- With MapActionOptions:
- namespace: the name of the module namespace
import { Vue, Component, MapAction } from 'types-vue';
@Component
export default class MyComponent extends Vue {
@MapAction()
changeTitle: any;
@MapAction({ namespace: 'counter' })
increment: (val: number) => void;
}
This is like:
export default {
methods: {
...mapActtions(['changeTitle']),
...mapActtions('counter', ['increment'])
}
}
If you are using Vuex, and you want to create a new module, you can use the Module decorator and extends the VuexModule class. You can call the Module decorator:
- Without parameters
- With ModuleOptions:
- name: the namespace name of the module;
- namespaced: a boolean value to set if you are going to use namespacing in this vuex module;
import { Module, VuexModule } from 'types-vue';
@Module({ namespaced: true })
export default class extends VuexModule {
counter: number = 0;
}
This is like:
export default {
namespaced: true,
state: {
counter: 0
}
}
This decorator adds the method to "getters" vuex module collection.
import { Module, VuexModule, Getter } from 'types-vue';
@Module({ namespaced: true })
export default class extends VuexModule {
_counter: number = 0;
@Getter()
counter(): number {
return this._counter;
}
}
This is like:
export default {
namespaced: true,
state: {
_counter: 0
},
getters: {
counter: function(state) {
return state._counter;
}
}
}
You can determine the access mode to the vuex store Getter
:
import { Module, VuexModule, Getter } from 'types-vue';
@Module({ namespaced: true })
export default class extends VuexModule {
_list: string[] = []];
@Getter({ mode: 'value'})
listValue(): string[] {
return this._list;
}
@Getter({ mode: 'reference'})
listReference(): string[] {
return this._list;
}
}
This is like:
export default {
namespaced: true,
state: {
_counter: 0
},
getters: {
listValue: function(state) {
return state._list;
},
listReference: function(state) {
return Object.assign({}, state._list);
}
}
}
This decorator adds the method to "mutations" vuex module collection.
import { Module, VuexModule, Mutation } from 'types-vue';
@Module({ namespaced: true })
export default class extends VuexModule {
_counter: number = 0;
@Mutation()
increment(value: number): void {
this._counter += value;
}
}
This is like:
export default {
namespaced: true,
state: {
_counter: 0
},
mutations: {
increment: function(state, value) {
state._counter += value;
}
}
}
This decorator adds the method to "actions" vuex module collection.
import { Module, VuexModule, Mutation, Action } from 'types-vue';
@Module({ namespaced: true })
export default class extends VuexModule {
_counter: number = 0;
@Mutation()
increment(value: number): void {
this._counter += value;
}
@Mutation()
decrement(value: number): void {
this._counter -= value;
}
@Action({ commit: 'increment' })
incr(value: number): number {
if (value < 0) {
return 0;
}
return value;
}
@Action({ useContext: true })
decr(context: ActionContext<any, any>, value: number): void {
if (value < 0) {
return 0;
}
context.commit('decrement', value);
}
}
This is like:
export default {
namespaced: true,
state: {
_counter: 0
},
mutations: {
increment: function(state, value) {
state._counter += value;
},
decrement: function(state, value) {
state._counter += value;
}
},
actions: {
incr: function(context, value) {
let result = (value) => {
if (value < 0) {
return 0;
}
return value;
}();
context.commit('increment', result);
},
decr: function(context, value) {
let result = (value) => {
if (value < 0) {
return 0;
}
return value;
}();
context.commit('decrement', result);
}
}
}
You can see a complete demo project in the demo folder of this project. The code is like the following:
// header-panel.ts
import { Vue, Component, Prop, Watch } from 'types-vue';
@Component
export default class Header extends Vue {
@Prop()
title!: string;
@Watch('title')
onTitleChanged(value: string) {
console.log('title is ' + value);
}
}
<!-- header-panel.vue -->
<template>
<div>
<h1>{{ title }}</h1>
</div>
</template>
<script src="./header-panel.ts"></script>
// demo.ts
import { Vue, Component, Watch } from 'types-vue';
import HeaderPanel from './header-panel.vue';
@Component({
components: {
'header-panel': HeaderPanel
}
})
export default class NotificationPanelComponent extends Vue {
title: string = 'Hello Vue from typescript';
@Watch('title')
onTitleChanged(value: string) {
console.log('in demo the title is ' + value);
}
}
<!-- demo.vue -->
<template>
<div>
<header-panel :title="title" :kk="title" />
<p>
<span>Change the title:</span>
<input type="text" v-model="title" />
</p>
</div>
</template>
<script src="./demo.ts"></script>
// counterModule.ts
import { Module, VuexModule, Mutation, Action, Getter } from 'types-vue';
@Module({ namespaced: true })
export default class extends VuexModule {
_counter: number = 0;
@Getter()
counter(): number {
return this._counter;
}
@Mutation()
increment(value: number): void {
this._counter += value;
}
@Mutation()
decrement(value: number): void {
this._counter -= value;
}
@Action({ commit: 'increment' })
incr(value: number): number {
if (value < 0) {
return 0;
}
return value;
}
@Action({ commit: 'decrement' })
decr(value: number): number {
if (value < 0) {
return 0;
}
return value;
}
}
// store.ts
import Vue from 'vue';
import Vuex, { Store } from 'vuex';
import counter from './counterModule';
Vue.use(Vuex);
const store = new Store({
state: {},
modules: {
counter
}
});
export default store;
// counter-panel.ts
import { Vue, Component, MapGetter, MapAction } from 'types-vue';
@Component
export default class NotificationPanelComponent extends Vue {
@MapGetter({ namespace: 'counter' })
counter;
@MapAction({ namespace: 'counter' })
incr;
}
<!-- counter-panel.ts -->
<template>
<div>
<p>The counter is {{ counter }}</p>
<button v-on:click="incr(1)">Add 1</button>
</div>
</template>
<script src="./counter-panel.ts"></script>
MIT