By John Papa
This post explores how I refactored the Vue app to use TypeScript. The two places I found the most help were in this (Microsoft resource](https://github.com/microsoft/typescript-vue-starter) and the Vue docs. Neither was exactly what I needed, but together they were helpful in gaining success. Read on to learn more.
Disclaimer: This was my first attempt at using TypeScript in Vue. My intent is to share the journey, not to claim this is the “best” way to use TypeScript. I know TypeScript well, but I am learning Vue and leaning on the experts from the Vue document and TypeScript team’s resources on Vue for much of what I accomplished. I give credit to both of these teams for making these resources available and helping the community.
Simply put TypeScript allows me to code faster and to catch and rectify more problems at development time than without it. Intellisense, auto-complete, and great tooling are enabled when editors can use the TypeScript features. Need some examples? OK … We can tell what the return type of an asynchronous function is an array of Hero models. Or we can find that we use a
heroes array in one place as an array and in another as an ES promise. When the editor is smarter, I can adapt quickly and fix problems before I deploy.
If you are interested in how I got started with Vue, here are some other posts that may interest you:
I had to start somewhere, so I started by branching off of my
connect2017 branch in this repo. This gave me a functional Vue app out of the box that I could begin refactoring to use with TypeScript.
I started by adding in the files that tell TypeScript how to do its job.
The first new file is
tsconfig.json. I started with the file that the Vue documentation recommends here. Then I enhanced it a bit. I found the changes to be super helpful.
./built folder, to generate source maps for debugging and use TypeScript decorators (among other settings). I also added in
lib support for some core ES2016 features.
I knew I needed to change how the build process worked. For this, I opened the
webpack.config.js and immediately started to Google for some help. I find WebPack configuration to be painful … hey, it’s an awesome tool, but I admit that it’s not easy for me to figure out what I need to modify in there. Luckily I found this guide by the TypeScript team that showed how to modify the file.
These are the git changes I made to my file.
We can see that the
entry file is now the
main.ts instead of
main.js. That makes sense. Then there are settings for a
ts-loader, which helps load the TypeScript with Vue. Finally, I added some extensions to the
Do I expect this is all perfect? Nope. It works … and this is the kind of thing that once the Vue CLI adds a TypeScript template to its tooling, we won’t have to mess with it at all. So let’s proceed.
I’m adding TypeScript to the project so it makes sense that I had to add some packages to support this. (The Vue CLI should handle this once it adds the new TypeScript template, too.)
First I ran
npm run typescript ts-loader --save-dev to add them as a development dependency. This came from the helpful Microsoft doc here. I deviated from that helped doc because I already had some of the others and I like to code dangerously.
Next, I ran
npm install vue-property-decorator vue-class-component --save to install support for the TypeScript decorators. The decorators will help define classes as Vue components and define
Note: I only ended up using one of these directly. The
@Component() decorator is in
vue-class-component but it is imported and re-exported from
We can define files that help TypeScript and the tooling know how to handle some specific types. The helpful Microsoft doc here suggested adding the following code to a new file named
vue.shims.d.ts. It basically makes it easier for the tooling to know how to handle
*.vue files, also known as Single File Components (SFC).
Refactoring the Code Files
A great way to look at the changes required during my refactor is to use a file by file comparison. Let’s begin.
main.js –> main.ts
I renamed the
main.js file to
main.ts. Yep, no code changes in this one. Move along.
The next image shows the TypeScript. Take a glance, then read on to learn how I refactored the code to get here.
First, I added a hint to the
tag to let it know I will be using TypeScript. Notice the tag is now
in the image below.
I added the
@Component() decorator to both tell Vue that the class following the decorator is indeed a Vue component and that this component will reference a child component named
components property in the decorator was lifted right from the
I refactored the default export to become a named class
App that extends
Vue. This makes sense as the name of our component is
App. We extend
Vue as a way of preparing our class to be a component. See an example in the Vue docs here.
I refactored the
data properties to become public class properties. Then I initialized the title property in the constructor. I could also have done this in a lifecycle event, which probably makes more sense.
Notice I added a few
import statements to the top of the file. This helped the tooling and TypeScript know where to get the
methods to make it easier to follow. Full source code can be found at the end of this post.
Here follows the TypeScript version of the file, after my refactoring effort. Take a moment to see the differences and how they map to each other.
Just as with the
App.vue file, the
components moved up to the decorator and the
data properties became public class properties. There are some subtle differences here though as we look closer. Notice that I added explicit types to
heroes. I did not need to do this, but it helped clarify things.
selectedHero was initialized to
null so the details wouldn’t appear until a selection had been mad. But now that I have a
(we’ll explore that in the next section), I wanted to be clear to the compiler of my intentions that the
selectedHero should be allowed to be
null or of type
But perhaps far more interesting is how I went from initializing the
getHeroes function … which was not an array of heroes, but instead a
promise of an array of heroes. This did not bite me in the running code because I was, but it could have. It worked only because the
getHeroes function sets the
heroes array … so it immediately ran and filled the array anyway. But this just hid the bug. When I refactored it to use TypeScript the editor alerted me that I had a type mismatch. Then I moved the call to
getHeroes to the
created lifecycle event. Ladies and gentlemen, that’s a real bug … not something I staged. Oops! But thankfully the editor caught my mistake and I was able to correct it during the refactor.
We also have some new imports up top in the TypeScript file and I added some more explicit typings in a few places, so I could make sure I had no more hidden bugs. That’s the value of setting
noImplicityAny: true in the
Now that I have types, I added a
hero.ts file to define a
Hero class. This helped make sure the components that deal with heroes are using the right types and properties.
HeroDetail component had many of the same changes as the
HeroList component. Some imports up top, the
@Component() decorator, moving the
data properties to public class properties, and defining some explicit types.
Now take a glance at the TypeScript version, after the refactoring effort.
What’s new here is that I am using the
@Watch() decorators in the TypeScript version. Notice the
@Prop(). This was a simple refactoring.
Then I defined a named function
onHeroChanged and decorated it with the
@Watch() decorator. This went from a function named
@Watch() decorator in the TypeScript version.
I access the
$refs to grab the elements to set focus on them. I made a public class property named
$refs and typed it with the names of the two references I had in the Vue template. Both are of type
I’m looking forward to the official TypeScript template for the Vue CLI. But in the meantime, there are a few ways to use TypeScript with Vue projects.
You can find the source code for these apps here:
- Angular / TypeScript app – in the
- Vue / TypeScript app – in the