Skip to content

Typescript decorators to make vue feel more typescripty

License

Notifications You must be signed in to change notification settings

noteon/vue-typescript

 
 

Repository files navigation

vue-typescript

Typescript decorators to make vue play nice with typescript

circleci npm npm npm GitHub stars

Why do i need this?

I built this mostly to remove the red squiggles on VSCode, but also to make Vue work seamlessly while still writing code that feels like typescript.

While you can just declare all your components as 'any' and carry on, you lose all the fantastic features of typescript. However, i also wanted to make sure i still had access to the entirety of Vue's features; so vue-typescript is built in such a way that you can pick and chose how much of it you want to use and what you would rather stick to standard Vue syntax for.

screenshot

Install

This package has one single peer-dependancy: Vue (obviously)

npm install --save vue-typescript

For the best experience you will want to use typings and typings install --save --global dt~vue as some decorators use these typings for input parameters. If you dont want to use them, the typed vue object will be handled as any.

Alternatively, clone the vue-typescript-seed repo

Features

  • @VueComponent - A class decorator that registers the class as a vue component
  • @Prop - A variable decorator that adds a class' variables to the prop object instead of data
  • @Watch - A variable or function decorator that adds a property to the watch object mapping the desired function as handler
  • Computed Properties - to define computed properties, simply use the native typescript syntax get and set (see example below)
  • @NoCache - to turning off caching for a specific computed property

Usage

@VueComponent

There are 4 ways to call it:

    @VueComponent
    @VueComponent(element:string)
    @VueComponent(options:ComponentOption)
    @VueComponent(element:string, options:ComponentOption)
     element - string to use as html tag
     options - the same object as the one you would use when calling Vue.component

By default, the tag will be the snake-case version of the class name, and options will be an empty object { }

@Prop

There are 2 ways to call it:

    @Prop
    @Prop(options:PropOption)

     options - the same object as the one you would use defining a prop

By default, the prop will behave equivalently to having  myProp: null   in the props object. However, if you give a value to a value to a variable decorated by prop (see example below), the default property of the prop object will be set to this value. If the value if of type array or object, don't worry about wrapping it in a function like you would do in standard vue, to provide maximum type checking and intelisense, vue-typescript clones and wraps it into a function for you.

@Watch

It can be applied to either a function or a variable, and for each application, there are 2 ways to call it:

    @Watch(name:string)
    @Watch(name:string, options:WatchOption)

     name - the name of the variable to watch (if applied to a function). Or the name of the method to call when the variable changes (if applied to a variable)
     options - the same object as the one you would use defining a property on the watch object (note that defining a handler is useless as it will get replaced in any case)

Behaviour

  • Variables - unless the @Prop decorator is used, all variables in a class will be returned by the data object
  • Functions - all functions that do not match the name of a Vue lifecycle hook, or are defined with the get and set kewywords, will be put in the methods object. Any function named like one of the hook (ex: 'ready()') will be added as a property of the options object and not to methods (see second example), while functions defined as get/set myFunc()... will be used to create the computed object.
  • Option Object - the option object allows to access features that may not have been implemented yet by vue-typescript, or simply offers the option to have a hybrid vanilla vue / typescript component. However, is a property is defined in bot the options obect and the class, the class variable will overwrite the one in options.
  • Constructors - Avoid defining constructors for classes that will be decorated by @VueComponent. An instance of the object is created at load time to create a vue object and register the component, if the constructor relies on parameters, there will be 'undefined' errors as these parameters will obviously not be passed.
    **see note on behaviour of new below

Examples

Pure Typescript:

import * as Vue from 'vue'
import { VueComponent, Prop } from 'vue-typescript'

@VueComponent
class MyComponent {
    @Prop someProp:string;

    @Prop({
        type: String
    })
    someDefaultProp:string = 'some default value'; 

    @Prop someObjProp:{some_default:string} = {some_default: 'value'}; //vue-typescript makes sure to deep clone default values for array and object types

    @Prop someFuncProp(){ //defined functions decorated with prop are treated as the default value
        console.log('logged from default function!');
    }

    someVar:string = 'Hello!';
    
    doStuff() {
        console.log('I did stuff');
    }
}

Is equivalent in Javascript to:

Vue.component('my-component', {
    props: {
        someProp:null,
        someDefaultProp : {
            type: String,
            default: 'some default value'
        },
        someObjProp: {
            default: function(){
                return {
                    default: 'value'
                }
            }
        },
        someFuncProp: {
            type: Function //if it finds the default is a function, it automatically sets the type
            default: function(){
                console.log('logged from default function!');
            }
        }
    },
    data: function(){
        return {
            someVar: 'Hello!'
        }
    },
    methods: {
        doStuff: function(){
            console.log('I did stuff');
        }
    }
})

Hybrid Object With Lifecycle Hooks:

import * as Vue from 'vue'
import { VueComponent, Prop } from 'vue-typescript'

@VueComponent('dope-tag', {
    watch: {
        'lookAtMe': function(old, new) {
            this.itChanged(new); 
        }
    }
})
class MyDopeComponent extends Vue { //extend Vue to get intelisense on vm functions like $broadcast()
    lookAtMe:string;

    ready() {
        this.lookAtMe = 'I\'ve changed';
    }

    itChanged(new:string) {
        this.$broadcast('New var: ' + this.lookAtMe);
    }
}

Is equivalent in Javascript to:

Vue.component('dope-tag', {
    data: {
        return {
            lookAtMe: undefined
        }
    },
    methods: {
        itChanged: function(new){
            this.$broadcast('New var: ' + this.lookAtMe);
        }
    },
    watch: {
        'lookAtMe': function(old, new) {
            this.itChanged(new); 
        }
    },
    ready: function() {
        this.lookAtMe = 'I\'ve changed';
    }
})

@Watch examples

Decorator on function:

@VueComponent
class MyClass {
    watchMe:string = 'look at meee!';

    @Watch('watchMe')
    myFunction(new_val:string, old_val:string){
        console.log('it was: ' + old_val);
        console.log('now it\'s: ' + new_val);
    }
}

is equivalent to:

Vue.component('my-class', {
    data: function(){
        return {
            watchMe: 'look at meee!'
        }
    },
    watch: {
        watchMe: function(new_val, old_val){
            console.log('it was: ' + old_val);
            console.log('now it\'s: ' + new_val);
        }
    }
})

Decorator on variable with options:

@VueComponent
class MyClas {
    @Watch('myFunction', {deep: true})
    watchMe:string = 'look at meee!';

    myFunction(new_val:string, old_val:string){
        console.log('it was: ' + old_val);
        console.log('now it\'s: ' + new_val);
    }
}

is equivalent to:

Vue.component('my-class', {
    data: function(){
        return {
            watchMe: 'look at meee!'
        }
    },
    methods: {
        myFunction: function(new_val, old_val){
            console.log('it was: ' + old_val);
            console.log('now it\'s: ' + new_val);
        }
    }
    watch: {
        watchMe: {
            handler: 'myFunction',
            deep: true
        }
    }
})

Computed Properties example

in typescript:

@VueComponent
class ComputedStuff {
    firstname:String = 'Jon';
    lastname:String = 'Snowflake';

    @NoCache
    get fullname():string {
        return this.firstname + ' ' + this.lastname;
    }

    set fullname(name:string){
        var name_arr = name.split(' ');
        this.firstname = name_arr[0];
        this.lastname = name_arr[1]; //you would probably want to add checks here to prevent errors
    }

    ready(){
        this.fullname = 'Jon Snowstorm';
    }
}

javascript equivalent:

Vue.component('computed-stuff', {
    data: function(){
        return {
            firstname: 'Jon',
            lastname: 'Snowflake'
        }
    },
    computed: {
        fullname: {
            cache:false,
            get: function(){
                return this.firstname + ' ' + this.lastname;
            },
            set: function(name:string){
                var name_arr = name.split(' ');
                this.firstname = name_arr[0];
                this.lastname = name_arr[1]; //you would probably want to add checks here to prevent errors
            }
        }
    },
    ready: function(){
        this.fullname = 'Jon Snowstorm';
    }
})

Integration With vue-router

vue-typescript works perfectly with vue-router:

import * as Vue from 'vue'
import * as VueRouter from 'vue-router'
import { VueComponent } from 'vue-typescript'

@VueComponent({
    template: '<h1> Welcome Home {{guy}} </h1>' //you can use require('./home_template.html') here if you're using something like webpack
})
class HomeView {
    guy:string = 'Frank';
}

Vue.use(VueRouter);

var app = Vue.extend({});
var router = new VueRouter();

router.map({
  '/' : {
    component: HomeView
  }
});

router.start(app, '#my-app');

Note on using new with component classes

Calling something like new MyComponent() will actually not construct a new MyComponent object, It will actually create a new vue component instance, with the properly formated data, methods, props, watch, etc.. objects. The class MyComponent is actually equivalent to the return of Vue.component('my-component').

Planned Features

  • scoped styles (in the decorator options)
  • @on / @once - function decorators to register event handlers
  • @VueDirective - define custom directives as typescript classes
  • @VueFilter - define custom directives as typescript classes
  • @VueExtend - classs decorator to create a vue subclass

Hacking It

Although its never recomended to make changes inside node_modules, if you find a bug that prevents you from moving forward and need to fix it ASAP. Just cd to the module directory, run typings install --production (this will ommit the mocha, chai, and node typings). Then make your fix and run npm run build. Your IDE might throw a fit because there's no tsconfig, to fix that rename tsconfig.build.json to tsconfig.json (simply run tsc if you do that, the build command points tsc to the .build.json file). Depending on how tsc decides to resolve packages, you might also need to npm install vue locally in the vue-typescript folder as well.
Don't forget to open an issue on the repo as well!

About

Typescript decorators to make vue feel more typescripty

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 66.9%
  • JavaScript 33.1%