Vuejs Typescript Best Practices 2

Salih Baki Sayer
4 min readJun 27, 2020
Best
Always search for the best ;)

This story contains best practices for Vuejs + Typescript projects that contains high business logic dependencies. Of course all of these recommendations are my own ideas which they are created by my own experiences so i am open to any of your ideas.

This is the 2. part of Vuejs Typescript Best Practices . You can look at the first part here :

1. Component Options

Best way to create vue component with typescript support is using vue-property-decorator. It creates class like syntax that makes easy to understand and group different parts of component options

Props :

@Component
export default class TestComponent extends Vue {
@Prop({type:Number,required:true}) readonly id?: number;
@Prop() readonly user?: Personnel;
@Prop({type: Boolean, default: false}) readonly isIconVisible: boolean;
@Prop({default:()=>[]}) readonly items: number[];
}
  • Set required true if that prop is required. That way your code editor will warn you or vuejs will throw some error at runtime.
  • If any prop is not required and don’t have a default value it must be defined as nullable. Otherwise it can cause undefined related errors at runtime.
  • Always set type for props , especially for booleans . If you set type of boolean props you can use is like this :
<test-component is-icon-visible />

Otherwise you have to write like this:

<test-component :is-icon-visible="true" />
  • object or array typed props default value must be a function that returns default type.

Watch functions that contains api requests

Let’s look at this example :

@Watch("user", {immediate: true,deep:true})
userChanged() {
const result = await UserService.getComments({userId:this.user?.id})
}
  • If immediate is true then this method will run at the created lifecycle method. If user prop is undefined then this method will run request with empty parameters.

So always check if field is not empty:

@Watch("user", {immediate: true,deep:true})
userChanged() {
if(!this.user?.id) return;
const result = await UserService.getComments({userId:this.user?.id})
this.comments = result;
}
  • userChanged can be triggered multiple times before previous ones resolved.

These multiple calls will run on parallel and this.comments will set multiple times. In the end, client will see the last resolved requests result . Not the last called result.

Exp:

first call : user.id = 2 and request starts and await response from server.

second call : user.id = 3 and request starts but response returns earlier than first call because user.id = 3 can have less comments from other user.

Then this.comments will be user.id = 2 comments because this request resolved late although it started before.

To prevent this result, either create a logic for canceling previous request or set the last called result. I use second solution:

counter:number = 0;

@Watch("user", {immediate: true,deep:true})
userChanged() {
if(!this.user?.id) return;

const counter = ++this.counter;
const result = await UserService.getComments({userId:this.user?.id})
if(this.counter != counter) return;

this.comments = result
}

Create a counter field at the component data. Increment counter and set it in local scope before request. After request check if the localcounter is equal to component counter. This will ensure last result to be used.

2. Vuex

Sharing states between multiple components is easy with vuex. But standart usage of vuex in components with typescript does not provide any type info.

this.$store has commit dispatch methods and state property. commit and dispatch methods uses string to decide which method to run at the store and state prop is any typed.

To solve this issue i use vuex-module-decorators . This package provides class like syntax at the vue modules. Also consuming vuex module at the component with type info is great with dynamic modules.

import { Module, Mutation, VuexModule } from "vuex-module-decorators";
import Store from "@/store";

@Module({
name: "error",
namespaced: true,
dynamic: true,
store: Store
})
export default class ErrorModule extends VuexModule {
errors: string[] = [];

get hasAnyError(): boolean {
return !!this.errors.length;
}

@Mutation
addError(message: string) {
...
}

@Action
async logErrors() {
...
}
}

Using in component:

const errorModule = getModule(ErrorModule);
@Component
export default class TheErrorModalComponent extends Vue {

get errors() {
return errorModule.errors;
}


addError(error:string) {
errorModule.addError(error);
}
async logErrors() {
await errorModule.logErrors();
}
onInput(value: boolean) {
if (!value) errorModule.clear();
}
}

This will prevent any kind of typo errors related to vuex module.

Conclusion

Best practices created by trial and error for me. Always look for the best way to write code for machine and human perspective.

Thanks for reading!

--

--

Salih Baki Sayer

Software developer, loves web dev , kendo (3.dan), books and games.