How I Got Here
My first time working on an SPA project was back in 2012. It was relatively a big-scale project that was based on hottest JavaScript library of the year — Backbone. Back then, the front-end development environment was not what it is today, and we’ve only started to hear murmurs about the concept of SPA. The whole of front-end camp was full of frameworks enslaved by MVC pattern. While people were arguing whether AngularJS conformed to MVC pattern or not, the React subspecies entered the playing field. React, framework influenced by reactive programming and functional paradigm, grew rapidly. While I was bellyaching over the fact that the genius minds that came up with this were younger than I am, a new framework called Vue also threw its name into hat. Vue was affected by many different frameworks, and while the code structure resembled React, it thrived when it came to deal with the states using reactive. Eventually, Vue built its reputation that rivals React by being easy to use. While web developers using React and Redux often run into issues in real-world problems even if they have complete understanding of the immutable foundation of React, the number of web developers using Vue is growing every day.
The field of front-end development is at the brink of transcending the limitations put forth by JavaScript’s linguistic restrictions. CoffeeScript, Flow, Babel, and TypeScript are such examples. Granted that Babel can be otherwise categorized because it only increases the compatibility of existing specs, CoffeeScript, language that led the revolutionary movement in the earlier times is facing golden retirement after leaving prominent bequeathal to ES6. Flow and TypeScript, supersets of JavaScript (ECMAScript), attempted to enforce a stricter type decree upon the original syntax, and in an attempt to do so, introduced a validation phase in the compile-time to the interpreter language that only had runtime. TypeScript eventually emerged victorious from the clandestine war between TypeScript and Flow, and TypeScript monopolized the transpile-language in JavaScript.
I personally do not have a positive outlook on stricter types, but still must admit that for protocols for diversity and module signatures specified with interface standards are quite useful. Although it splits the JavaScript fandom into two, with all of the advancements being made every day, JavaScript is shaping up to be a more standardized language every day. However, if you start to criticize the language for the lack of types, I don’t think I can have JavaScript’s back and give you a reason why JavaScript is the best programming language there is! Only statement I can offer is that the JavaScript language is “meh.” I cannot say for sure that TypeScript will be the “in” thing, or if another CoffeeScript will take its place, but it is my opinion that if you want to learn front-end development, you should take an interest and educate yourself with a little bit of TypeScript. That’s exactly what I did when it was first released, but until recently I was happy with simply knowing about it, and had no intention what-so-ever to use it at my workplace.
Years have passed after I first learned TypeScript, and just as TypeScript was about to become thing of the past, my brain played a cruel trick on me and made it interesting to me again. Not only that, but TypeScript was being used in the office, not by front-end developers but developers in other fields; TypeScript has widened its scope. Developers who were used to dealing with strict types used TypeScript as a buffer to start using JavaScript. Because using types is not only the matter of using the types or not, but it also affects the design of the entire application, it was understandable. Even without the request from the company, we realized that we needed a TypeScript virtuoso. Therefore, we decided to use TypeScript for the official release of a project we were working on at the moment. For that particular project, I started using Vue for the same reason.
Vue and TypeScript are affable to the extent that Vue rewrote the entire 3.0 codebase in TypeScript. Even as I was working with 2.x version of Vue, it was predictable that Vue would also get along well with TypeScript. The first issue I had to consider was the choice between object-based component and class-based component. I eventually decided that I lacked sufficient experience to make the call, and decided to use object-based for the lowest base components and class-based components for the rest. I feared that, because the number of base components was smaller than that of others, it would eventually become a class-based component project. As I was working on the project, I realized how big of a role Vuex played, and this article is written specifically to share my experience as well as the result.
JavaScript and Vue Component
I apologize for the lengthy introduction. To be fair, I simply cannot believe how much has changed and how far the world of development has come. The codes that I am about to share with you are in files incomprehensible by SFC browsers, and the language is not even technically JavaScript. The codes presented to you in the browser when you’re debugging are not the codes that are used, but only are mere ghostly figments of the restructured source map. Even as I am describing this, I am getting nostalgic, so without further ado, I will continue on with the article.
Single File Component (SFC) is a file exclusively for Vue component that is recommended by Vue. The template, JavaScript, and even CSS is defined all in one file. When developers are working with Vue, instead of defining classes to build components, developers can simply define options to build such classes with.
<template>
<div>
<input type="text" v-model="newTodo" @keyup.enter="onEnter">
<ul>
<li v-for="todo in todos">{{todo}}</li>
</ul>
</div>
</template><script>
export default {
data() {
return {
todos: ['TASK1'],
newTodo: ''
};
},
methods: {
onEnter(ev) {
this.addTodo(this.newTodo);
},
addTodo(title) {
this.todos.push(title);
}
}
};
</script>
What you see above is a simple example of a Todolist component. The piece of data that holds the Todo list is an array called todos
, and it stores the elements as strings. In the template, the todos
array is automatically iterated and the Todolist is depicted using the li-element. If a new task is entered into the input box, it updates the todos
array. It is a simple enough component, and works appropriately. Now, let’s modify it using TypeScript.
Vue.extend
There are two ways to implement TypeScript onto the Vue component. One is using Vue.extend
to objectify it, or you can simply make Class-based components. For the example, in order to fully differentiate the components’ characteristics, I used both methods to facilitate easier understanding. Let’s first take a look at using Vue.extend
method. When the component is defined using the Vue.extend
, it looks similar to the version of the component without the TypeScript.
<template>
<div>
<input type="text" v-model="newTodo" @keyup.enter="onEnter">
<ul>
<li v-for="todo in todos">{{todo}}</li>
</ul>
</div>
</template>
<script lang="ts">
import Vue from 'vue';export default Vue.extend({
data() {
return {
todos: ['TASK1'],
newTodo: ''
};
},
methods: {
onEnter(ev: UIEvent) {
this.addTodo(this.newTodo);
},
addTodo(title: string) {
this.todos.push(title);
}
}
});
</script>
Define the Vue component the same way as you would with JavaScript by using the component constructing-option object. However, with Vue.extend
, you can use types. If you are using Visual Studio Code, you can check the summary of types by hovering on extend, and you can even peek into files and signatures using Peek Definition as in the image below.
TypeScript can be integrated onto Vue projects to develop components by using the extend
in the type declaration defined in @type
directory. Warning alerts for illegal component options as well as alerts for misusing APIs or component members are functioning properly, and the types for data are appropriately assumed. When I was running simple tests like these in the beginning, I was as happy as a clam at high tide, but, my happiness was short-lived.
interface Todo {
title: string;
}
Here, I declared the Todo type for the component data. I did this with the intention of receiving the data of the Todo type from the parent component as props. I had no reason to doubt that this would not work, so I declared that I would use the default Todo type of the array with Todo[].
export default Vue.extend({
props: {
todos: {
type: Todo[],
required: true,
default: []
}
},
...
However, this code soon runs into an error.
The error states that the Todo is being used as a value when it should only be used to refer to a type. Which means that when I declared type: Todo[]
, I did not declare the type, but rather assigned the value Todo[]
to type
. After thinking about it, the syntax made sense; the types defined in TypeScript are only useful during the development and compile stage, but do not exist in the transpiled JavaScript code. My logic worked with JavaScript because Number
and Array
were primitive values that I could actually use. There are articles suggesting resolutions, but none have swept me off my feet yet.
I still have not found an attractive solution to the problem that in TypeScript, when defining the component in object-form, TypeScript types cannot be used as props’ type. For the current project, I only use object-form to define the lowest components, and for the same reason I just described, I only use JavaScript’s provided basic types.
export default Vue.extend({
props: {
title: {
type: String,
required: true,
default: []
}
},
I ran into another problem when I introduced Vuex to my project. This kind of problem also occurs in other class-based components. Vuex’s Map-Helper functions conveniently map the features related to the various data defined in the store to be the members of the component. Map helpers, based on JavaScript, were used constantly for almost every one of store’s functionality. However, in TypeScript, using Map-Helpers changes the methods and data to String and other Map-Helper options, making it difficult for TypeScript to determine that a certain member exists in the component.
methods: {
...mapActions(['addTodo']),
onEnter(ev: UIEvent) {
this.addTodo(this.newTodo);
}
}
Therefore, Map-Helpers cannot be used. I would have to manually create an indirection method, or for actions, manually executing the dispatch in order to access the store.
methods: {
addTodo(todo: string) {
this.$store.dispatch(‘addTodo’, todo);
},
onEnter(ev: UIEvent) {
this.addTodo(this.newTodo);
}
}
Eventually, I concluded that Vue.extend
is not a good combination for TypeScript.
Class based component
Since using Vue.extend
and objects had issues with TypeScript, I theorized that when using TypeScript, it would be better to design the components in classes. I believed Vue.extend
is a method that puts more weight on the Vue framework as a whole, and class based components put more weight on the language itself. While you could use class based components in ES6, since Vue is built with the foundation that components are created using component constructing option objects, I maintain that object form is still appropriate in ES6. Although I have no idea how things will change in Vue 3, I would like to believe that Vue and I share the common belief. The discussion on class based Vue component does not yet have a concrete answer, but is rather being continuously debated. Perhaps, without TypeScript, such discussion may not have even emerged.
<template>…</template><script lang="ts">
import {Component, Vue, Prop} from 'vue-property-decorator';
import {mapActions} from 'vuex';@Component({
methods: {
...mapActions(['addTodo'])
}
})
export default class Todolist extends Vue {
public newTodo: string = ''; public addTodo!: (title: string) => void; @Prop({required: true})
public todos!: Todo[]; public onEnter(ev: UIEvent) {
this.addTodo(this.newTodo);
}
}
</script>
When dealing with Classes, we use get
and set
to define the computed properties, methods are used directly as Class methods, and data field from the Class can be used as is in Vue. Other concepts such as watchers and props can be used in Vue through decorators. Decorators provide options available in Vue to Class-Based components. Using the Class-Based components allows us to easily deal with the types of component properties.
@Prop({required: true})
public todos!: Todo[];
Here, I used the Prop decorator to define that todos is a prop, and passed it as an input. Also, type can be declared as an intact syntax of TypeScript, instead of being declared as just an option. I think that, beside the fact that vue-property-decorator is not maintained by the official Vue developers, the Prop decorator is a stable and clear solution to our problems. Now, when we are using Props in components, we can use types to our advantage.
However, Vuex Map-Helper has become yet another bane of our code. I attempted to use mapAction helper by using Component decorator.
@Component({
methods: {
...mapActions(['addTodo'])
}
})
Let’s assume that we have already clearly defined the signature of store’s addTodo action using TypeScript’s type. Even if that is the case, because addTodo has been passed as an input through the String, the component cannot verify that addTodo exists. This is why when we try to use the addTodo component immediately after, the error message has the nerve to say that we are trying to use a method not defined in the component. While we had no way of using the Map-Helpers with the Vue.extend
method, we do for Class-Based components! We simply define the signature again within the class.
public addTodo!: (title: string) => void;
We cheated by forcing the method’s signature redundantly. The cost for my repetition can not only be found in the logic of my code, but also in the type definition. We should make it so that we only define the type once, and let the compile figure out what the type is. Even though I spent much of my time searching for other possibilities, I had no luck.
Conclusion
In the end, I decided to adhere to the Do-not-Repeat-Yourself (DRY) Principle, and decided against using the Map-Helpers. Mapping store elements using Map-Helpers, depending on the component, created long codes to begin with, but now with type repetitions, the code became unnecessarily complicated. If you also consider having to change types in different situations, I was petrified. The components of my current project look like the following.
<template>…</template>
<script lang="ts">
import {Component, Vue, Prop} from 'vue-property-decorator';@Component
export default class Todolist extends Vue {
public newTodo: string = ''; @Prop({required: true})
public todos!: Todo[] get schedule(): Schedule {
return this.$store.state.schedule;
} public onEnter(ev: UIEvent) {
this.$store.dispatch('addTodo', this.newTodo);
}
}
</script>
Instead of using the Map-Helpers, I decided to define my own Vuex coding convention.
- Unless forced in special situations, use dispatch and commit methods to directly call action and mutation instead of using indirection methods. If a wrong type were to be passed as an input, we would not be able to make use of it, but this would allow us to eliminate type repetition; furthermore, if inaccurate action or mutation executes, the framework will let us know by raising an error.
- While
state
andgetter
can be defined ascomputed
under certain circumstances o be used directly through$store
, avoid using$store
in the template itself. It only adds complexity to the template.
Following my own convention, developing Vue components in TypeScript environment has been a breeze. Seeing that I have not had any major issues even in the endgame of the project, I think it is safe to say that the pattern will likely continue at least until Vue 3 is released.
At the moment, I think TypeScript is better suited with React than it is with Vue There are major pieces of information I have to support the claim aside from this article. TypeScript supports JSX under the name of TSX, allowing TypeScript to verify the JSX components, but it still does not support SFC or templates of Vue. Therefore, no matter how foolproof you design the type of the component Props, it will never be whole with the current state of Vue. I personally conclude that TypeScript and Vue framework need more time together, but Vue is very much in favor of TypeScript to the point that the entire code base of Vue 3 has been developed in TypeScript. I think we can be hopeful that Vue 3 will present more attractive options.
Not to long ago, in the RFC document discussing the direction the Vue should be headed on, Class API RFC has been added. While I agree that it should be discussed more in depth, I also believe that, so far, it looks good. I recommend anyone who is interested to check it out, and if you have any other interesting alternatives, I welcome it with open arms.
Originally posted at Toast Meetup written by Sungho Kim.