Vue.js Typescript Best Practices
Main reason of this story is to help my colleagues and myself to remember some best practices for writing clean and readable codes. For organization i am going to divide this article to three main parts ; Component template, Component script , Business Logic
Component Template
Component template part is the where we write html and combine it with some javascript . Important thing to remember is this part is not type checked at development or build stage . So if there is any typo here it will only be seen at runtime.
Minimal javascript is the key .
Don’t Use Complex Javascript
<data-label
:value="
currentActivityOffer.owner.name +
' ' +
currentActivityOffer.owner.surname
"
class="flex-1 justify-content-center"
label="Owner"
/>
Problem here is currentActivityOffer variable can be empty or doesn’t have any property named owner. Using computed property here is better option . Correct way :
<data-label
:value="currentActivityOfferOwnerNameSurname"
class="flex-1 justify-content-center"
label="Owner"
/>
Use Same Naming Convention and Attribute Order for Components Across the Entire Project
My suggestion is follow vue.js naming conventions . component names , custom properties , custom events kebap-cased . This makes reading more easy.
<my-component
v-if="isVibile"
v-model="myModel"
:my-awesome-prop="myProp"
@my-event="onMyEvent"
/>
Component Script
We are using class typed vue component with vue-property-decorator .
Dont Write Business Logic Inside Component Functions
By function i mean functions and computed properties. Component functions must only contains code that effects component state or calls one function . This will help writing test for your component and logic function easy.
get doseCalculationMultiplier() {
return (product: OrderDrugProductModel) => {
if (
!product.doseIntegrityType ||
product.doseIntegrityType != EnumDoseIntegrityType.Calculated
)
return 0;
if (!product.doseCalculationType) return 0;
.
.
.
};}
This code is hard to read and component doesn’t need to know this logic. Extracting this code to some helper class is better option
get doseCalculationMultiplier() {
return (product: OrderDrugProductModel) =>
OrderDrugHelper.doseCalculationMultiplier(product, this.model);
}
Computed Property and Watcher
Use these two correctly.
If you need to watch and react to change of more than one prop or data instead of writing two watch function , create a computed property and watch it .
name:string = '';
secondName:string ='';@Watch("name")
onNameChanged(){
...
}@Watch("secondName")
onSecondNameChanged(){
...
}
Right way :
name:string = '';
secondName:string ='';get fullName(){
return `${this.name} ${this.secondName}`
}@Watch("fullName")
onFullNameChanged(){
...
}
And dont write any code that changes state in the computed property , this can lead some infinite loop or performance issue.
Business Logic
Our project highly depends on business logic and contains many interfaces and service functions. To organize this we divided code by modules , models and type
Directory Structure
Every domain model must have it’s own directory . Inside the directory file must divided by their roles ; data type, communication with service , mapping types , helper , validation .
Example for Personnel model
- personnel.interface.ts (data type)
- personnel.service.ts (communication with backend)
- personnel.mapper.ts (mapper functions if needed)
- personnel.helper.ts (some helper functions)
- personnel.validator.ts (domain spesific validation before sending data to backend)
Functions
- Function names and argument names must be clear .
caclPersName(pers:Personnel):Personnel{
...
}
Right way :
calculatePersonnelName(personnel:Personnel):Personnel{
...
}
- Always add return types
async getPatient(id:number){
...
}
Right Way:
async getPatient(id:number):Promise<Patient>{
...
}
- Functions must do only one thing
If function has code that does more than one thing create another function for each task and use them
- Return Early
Don’t use interwined if blocks , return early and keep it clean
coolFunction(param:string,param2:boolean):void{
if(param)
{
if(param2)
{
...
}
else{
throw ...
}
}
else{
throw ...
}
}
Right way:
coolFunction(param:string,param2:boolean):void{
if(!param) throw ...
if(!param2) throw ...
...
}
- Dont use complex if statements
if(param.indexOf('text') > -1 || param2 % 20 === 0)
{
...
}
Right way:
let paramContainsText:boolean = param.indexOf('text') > -1;
let canParam2DividedBy20:boolean = param2 % 20 === 0;if(paramContainsText || canParam2DividedBy20)
{
...
}
- Use object as parameter if functions has a lot of arguments
Some functions need a lot of arguments and some of these arguments can be optional .
Well , look at this amazing function and its usage 😆 :
static async getDocumentProcesses(
visitId?: number,
take?: number,
skip?: number,
code?: string,
id?: number,
documentCategory?: string,
search?: string,
keys?: number[],
include?: string[],
orderBy?: string,
documentType?: EnumDocumentType,
patientId?: number,
getAll?: boolean,
clientId?: number
)
..----------------------------------------------------------------getDocumentProcessesFull(
undefined,
undefined,
undefined,
this.documentCode,
undefined,
undefined,
this.visit.patientId,
undefined,
undefined,
true
);
Right way :
static async getDocumentProcesses(
options:{
visitId?: number,
take?: number,
skip?: number,
code?: string,
id?: number,
documentCategory?: string,
search?: string,
keys?: number[],
include?: string[],
orderBy?: string,
documentType?: EnumDocumentType,
patientId?: number,
getAll?: boolean,
clientId?: number
}
)
..-----------------------------------------------------------------getDocumentProcessesFull(
{
code:this.documentCode,
patientOd:this.patientId
}
);
Conculusion
Every unit (Function , component , class) must contain simple and uniq code that aligns with their definition . This way reading , debuging , improving and testing that unit will be easy.
And another important thing for me is to constantly search ways to write more clean and better code . So any opinion is realy important.
Thanks.