You are on page 1of 81

Why Vue &

TypeScript
Introduction
One of the big topics being discussed in Vue 3 is around
TypeScript. However, for those of us who haven’t jumped
on the TypeScript train yet, there is understandably a lot of
hesitancy when it comes to adding TypeScript to their
codebase. In this lesson, I’ll be addressing some of these
concerns to help provide another perspective when
evaluating whether Vue & TypeScript is a good
combination for your project or not.

Defining TypeScript
First, let’s take a minute to level set as far as what
TypeScript is.

From the ​TypeScript website​, “TypeScript is an


open-source language which builds on JavaScript, one of
the world’s most used tools, by adding static type
definitions.”

If you haven’t worked with static types before, let’s take a


normal JavaScript variable called mysteryBox.

JavaScript, by default, is what is called a “dynamically


typed” language, which means the variables can be
assigned any “type” of value. That means our mysteryBox
variable could contain a string, number, boolean, array,
object, or something else entirely!

While there are a lot of benefits to this, the downside is that


it becomes difficult to predict how something should (or
should not) behave. So in the event we had a variable
called ​myAge​, we know that this should always be a number;
but without any other tooling, the only way for us to check
this would be to always add conditional statements
whenever we use the variable ​myAge​.
This is where static types have a lot of value, because they
never change and are fixed values, they allow us to define
types ahead of time so we can trust that variables like
myAge always behave the way we want them to.

With that said, let’s get into the pros and cons of adding
TypeScript to your Vue project.

The cost of
TypeScript
Like any additional dependency you add to your codebase,
the biggest cost is typically the learning curve required for
new developers to become productive with the new tool.
As more and more developers come into the world of
programming with JavaScript as their first language, this
means that there is a lot of new paradigms and things to
learn which can be very expensive to teams with tight
deadlines.

Let’s be honest, using TypeScript in a codebase seems like


it is more than just adding a library to handle a specific part
of the application. After all, when we hear most people talk
about TypeScript, their entire application is often written in
it and it’s probably hard to imagine spending time rewriting
your codebase when most of us don’t even have time to
refactor the technical debt that already exists! And of
course, even when people have added TypeScript to their
codebase, it’s not as if everything is suddenly better and
their code is suddenly bug-free.

Like any tool or technique, there are compromises and


trade-offs that often require experience and often lessons
learned in order to get to a happy place for the team.
Having heard all of this, the question here is, is it worth it?

TypeScript are not


as scary as you
think
The first concern many often have with TypeScript is that it
feels different and strange. Given that many of us are used
the dynamic typing of JavaScript, learning a new syntax
and paradigm for how we define and write code
understandably feels odd. However, for those who have
used Vue for a little bit, the truth is that chances are very
high that you’ve been working with types this entire time.
Don’t believe me? Let’s dive into a component.

Here we have the proverbial button component which is


supposed to receive text and the background color as
props.
While many of us started by defining props in the array
syntax, we see here, most of us converted to using the
object syntax to better define our props.
And then for those of us who know about custom
validators, we often went one step further to add custom
validators to ensure we received the component received
the correct data.
After looking over this new and improved version of props
on our button component, what you might have realized
now is that what we’ve done is assign a “static type” to our
props.

In other words, whether you realized it or not, you’ve been


leveraging static types this entire time!

Is TypeScript worth
it?
Of course, while we’ve now demystified the use of static
types, that still leaves the question of whether it’s worth it
to add TypeScript to your codebase.

While TypeScript certainly has lots of benefits when it


comes to type checking and such, like I mentioned earlier,
the key thing to remember is that all tools have
compromises and trade-offs. So if I am going to add
additional tooling to a codebase, I want to make sure that it
solves a specific problem that I or my team is having.

In my opinion, when a team has little to no experience with


TypeScript, the value that TypeScript provides a codebase
ultimately comes down to one thing: scalability.

Scalability can be broken down into two primary


categories:

1. How large your codebase is?


2. How many users are contributing to your codebase?

Large Codebases
When we look at Angular, which is a highly opinionated
framework that was designed primarily with enterprise
companies in mind, their decision to make TypeScript
mandatory is something worth considering.

As a codebase grows in size, this often means that there is


greater complexity in the moving pieces that ultimately
make the application work. Because of this, TypeScript’s
ability to provide reliable data structures that developers
can trust can be critical to a team’s productivity.

This is why many companies find themselves investing in


TypeScript since it helps to provide that additional layer of
documentation to code that otherwise might otherwise be
lost if the original contributor leaves the company and
forgot to add comments to the code they wrote.

Large Number of Contributors


On the other hand, there are those who be thinking, “Well
my application is small, so how would TypeScript benefit
me?”

For people in this scenario, TypeScript is particularly


beneficial if your application has a large number of
contributors. The most common scenario for this is if you
maintain an open source project. Since people could be
contributing to an open source project at any given time,
the ability to provide structure and consistency to
contributors is critical.

After all, in addition to making sure the codebase works as


expected, it also has the added benefit of reducing the
amount of time maintainers have to spend answering
questions or resolving PRs that would have been solved
with properly defined types.

What about
TypeScript support
in Vue?
One of the biggest hurdles that people encountered when
using TypeScript in Vue 2 was that while it was technically
possible, there was a rather high cost of entry and some
things didn’t work as well as they’d like. For example,
being able to easily detect types across Vuex was lacking
and people had to make various compromises to get it to
work.

When hearing this, it’s no surprise that many have avoided


adding TypeScript to their Vue projects. For those who
don’t know though, Vue 3 was rewritten entirely in
TypeScript. As a result, developers can expect better
support for TypeScript going forward. So whether it’s
developer tooling such as Vetur or libraries such as Router
and Vuex, using TypeScript will become much easier. And
as a result, the cost of entry is a lower than ever before.

TypeScript can be
progressively added
A misconception that many have is that TypeScript is all or
nothing. After all, if you’re going to use TypeScript,
shouldn’t your entire application be rewritten in it? The
reality is that this couldn’t be further from the truth. In
other words, just like how you can progressively migrate a
legacy app to Vue by dropping in a CDN package to add
Vue functionality, the same can be said of TypeScript.

So rather than feel pressure to rewrite your entire app in


TypeScript, I recommend choosing a specific feature where
you will enhance it with TypeScript to see whether it’s
worth it for your team or not. And if you and/or team see
the benefits of TypeScript, then keep slowly refactoring as
it makes sense!

Let’s ReVue
At the end of the day, it’s possible that TypeScript is not a
good fit for your project. And if that’s the case, that’s
perfectly okay! However, if it seems like TypeScript might
have a place in a future project, then be sure to stick
around as we dive into the fundamentals of working withe
TypeScript in Vue.
Setting Up
Vue 3 &
TypeScript
Introduction
In this lesson, we will be talking about how to setup a
brand new Vue project with TypeScript and how to add
TypeScript to an existing Vue 3 project.

Prerequisites
The first thing we need is the Vue CLI. If this is the first
time you’ve used Vue CLI, here are the basic steps for
getting setup:

1. Visit the official docs - ​https://cli.vuejs.org/


2. Click on the ​Installation page​ to get the latest
command

At the time of this lesson, I am using Vue CLI v4.5.8.

Setting Up a New
Vue.js 3 +
TypeScript Project
Creating Our Project in Vue
CLI
Inside of your terminal, let’s create a new project by
running
When prompted to select a preset, let’s choose the options
manually to understand what’s going into our project by
using the arrow keys to select the third option: .

You should now be prompted to choose the features


needed for the project. By default, you should see ​Choose
​ abel​,
Vue version​, B and ​Linter / Formatter​ already selected.

Since we also want TypeScript for this project, use the


arrow keys to navigate down to the ​TypeScript​ option and
hit the spacebar in order to select it. You should see the
circle fill up with a smaller green circle which confirms you
correctly turned it on.

When prompted to choose the version of Vue you want to


install, you should select ​3.x (Preview)​.

For the remaining configurations, we’ll want to choose:

● Use class-style component syntax? ​No


● Use Babel alongside TypeScript? ​Yes
● Pick a linter / formatter config: Up to you, but I like
ESLint + Prettier
● Pick additional lint features: ​Lint on save
● Where do you prefer placing config files? ​In
dedicated config files
● Save this for future project? Up to you, but I chose
No

Once everything has been setup, let’s open up our project


in our editor.

An Overview of the Vue


Project
When we open our project inside of our text editor, one of
the first changes you may notice is that ​main.js​ is now
main.ts​. As part of the TypeScript plugin, it will
automatically generate TypeScript files (indicated by the
.ts​ extension) rather than JavaScript files (indicated by
.js​).

However, when we open up the file to see what has


changed, you will see that nothing has actually changed.
After all, TypeScript is meant to enhance JavaScript files
when needed, but when it’s not needed, standard
JavaScript is still perfectly valid!

One of the new files that will definitely be a first if you’ve


never used TypeScript in other projects before is the
shims-vue.d.ts​. And while the contents of the file might
look foreign, you’ll be relieved to know that it’s primary
purpose is to provide TypeScript some information about
Vue components. In other words, it’s boilerplate and you’ll
never need to configure it unless you have some advanced
and complex scenario

There’s also a ​tsconfig.json​ which contains some default


configurations for working with TypeScript.

Adding TypeScript
to an Existing Vue
CLI Project
When working with an existing Vue CLI project, we can
make use of the TypeScript plugin by calling:

● vue add typescript

When prompted here are the options we want to configure:


● Use class-style component syntax? ​No
● Use Babel alongside TypeScript? ​Yes
● Convert all .js to .ts? ​Yes
● Allow .js files to be compiled? ​No
● Skip type checking of all declaration files? ​Yes

Once that’s done, you should see that your application now
has the boilerplate files (i.e., , , , , ) updated along with any
other JS files you might have. However, if you have other
components, they will not be updated since Vue CLI does
not want to make assumptions on whether it makes sense
to enhance components with TypeScript or not.

Final Thoughts
As we can see in this lesson, Vue CLI makes the process of
adding TypeScripts much easier since it takes care of a lot
of configuration that you would normally need to do
manually.
Creating
Components
with
TypeScript
Introduction
In the last lesson, we learned how to setup a project with
TypeScript and Vue CLI. In this lesson, we’ll take a look at what
changes have happened inside of our Single File Components.

TypeScript Changes
in Single File
Components
The Language Attribute
One of the most important changes that have happened in our
Single File Components (SFC) is that our block now contains a
attribute, which stands for language. This is more commonly
seen on the block where we declare the use of pre-processors
such as Sass.

Given that we want to use TypeScript with our SFCs, this


means we also need to configure that on our block by assign
the value of to the property.
● <script lang="ts">

defineComponent Method
One of the newer things that takes some getting used to in Vue
3 is the importing of helper methods. And while we typically
define components in Single File Components (SFC) by writing:

● export default { ... }

However, when we want to use TypeScript, we need to be


explicit with TypeScript that we are declaring JavaScript specific
to the SFC. As a result, we have to use a helper method from
Vue called .

● import { defineComponent } from 'vue'

Once we have this, you’ll see that we use it by passing the


object we typically export inside of this function:

● export default defineComponent({ ... })


Upgrading Real
World Vue App
Component
To put what we’ve learned in practice, we will be enhancing the
components from Adam Jahr’s ​Real World Vue 3​ app that he
builds in the course.

To get started, check out the ​Real World Vue 3 TypeScript


GitHub Repo​. In here, the project has already been upgraded,
and we’ll start upgrading our components in here for exercises.

For this lesson, we’re going to upgrade the together.

<​script​>
import​ EventCard ​from​ ​'../components/EventCard.vue'
import​ EventService ​from​ ​'../services/EventService'

export​ ​default​ {
​name​: ​'EventList'​,
​components​: {
EventCard
},
data() {
​return​ {
​events​: ​null
}
},
created() {
EventService.getEvents()
.then(response => {
​this​.events = response.data
})
.catch(error => {
​console​.log(error)
})
}
}
</​script​>

Following the steps from before, we need:

● Defining the attribute on the block


● Import the helper method
● Pass the exported object to

And when we’re done, it should look like this!

<​script​ ​lang​=​"ts"​>
import​ { defineComponent } ​from​ ​'vue'

import​ EventCard ​from​ ​'../components/EventCard.vue'


import​ EventService ​from​ ​'../services/EventService'

export​ ​default​ defineComponent({


​name​: ​'EventList'​,
​components​: {
EventCard
},
data() {
​return​ {
​events​: ​null
}
},
created() {
EventService.getEvents()
.then(response => {
​this​.events = response.data
})
.catch(error => {
​console​.log(error)
})
}
})
</​script​>

Challenge
For this lesson, I challenge you to upgrade the ​EventCard.vue
and ​EventDetails.vue​ components. You can find the starting
point for the challenge on the ​03-challenge​ branch.

Good luck!
Type
Fundamental
s
Introduction
One of TypeScript’s fundamental concepts is the ability to
define static types that can be used to enhance development. In
this lesson, we will be covering some of the most useful types
that you should be aware of as you start your TypeScript
journey.

Overview of Types
Basic Types
As a quick review, common JavaScript types that we often
encounter in our code include:

● String
● Number
● Boolean
● Array
● Function
● Object

These form the foundation of most applications built with


JavaScript, but in the world of programming, there are many
other data structures that are worth knowing. To learn more
about what types and data structures exist in JavaScript, check
out ​MDN’s documentation on JavaScript Data Types and Data
Structures​.

New Types from TypeScript


While JavaScript comes with a lot of native types, TypeScript
also offers additional types commonly found in other
programming languages such as:

● any​ - allows you to assign any type to the variable, which


is the basic equivalent of disabling type checking
● tuple​ - allows you to define an array that contains a fixed
number of elements with certain types
● enum​ - allows you to define friendly names to sets of
numeric values

We won’t be going over these types in this course, but if you


want to level up your TypeScript abilities, you’ll definitely want
to check out the ​official documentation on TypeScript Basic
Types​.
How to Apply a Type
to a Variable
Now that we are familiar with these basic types, the natural next
question is: “How do we apply these types to our variables?”

String, Number and Booleans


In TypeScript, this is accomplished by appending our variable
with the ​:​ and the desired type. Here are some examples using
some basic types:

let​ stageName: ​string​ = ​'A Beautiful Vue'

let​ roomSize: ​number​ = ​100

let​ isComplete: ​boolean​ = ​false


Arrays
In TypeScript, defining arrays is not as straightforward as
saying:

let​ shoppingList: array = [​'apple'​, ​'bananas'​,


'cherries'​]

Because TypeScript is about being more explicit about what


types are expected in the array, the notation for defining arrays
is a little bit different. Using our ​shoppingList​ example from
above, we know that the list should only include a strings. As a
result, we define the type with:

let​ shoppingList: ​string​[] = [​'apple'​, ​'bananas'​,


'cherries'​]

Function
When it comes to adding types to a function, there are a few
ways to do this. However, regardless of the methodology, there
are two key parts to keep in mind:

● Parameters
● Return

For the purposes of getting familiar with adding types to


functions, we will be using the ES6 arrow function with a
generateFullName​ method. Before adding any TypeScript, it
might look something like this:

let​ generateFullName = (firstName, lastName) => {

​return​ firstName + ​' '​ + lastName

However, as you might have guessed, this leaves us vulnerable


to having random data types being passed into our function
when we really only want strings to be passed in. So as a first
step, we would define the types expected on our parameters,
which in this case are strings.
let​ generateFullName = (firstName: ​string​, lastName:
string​) => {

​return​ firstName + ​' '​ + lastName

However, we’re not done yet! The one last thing we need to do
is to define what type of data we expect to get from the function,
which we do by using the colon (i.e., ​:​) after the parameters.

let​ generateFullName = (firstName: ​string​, ​lastName​:


string​): string => {

​return​ firstName + ​' '​ + lastName

Object
In TypeScript, objects are where things begin to get more
interesting, but before we get into those pieces, let’s start with
the fundamentals of how to define types on object values. In the
case of a person object:

let​ person = {

​name​: ​'Peter Parker'​,

​age​: ​20​,

​activeAvenger​: ​true​,

​powers​: [​'wall-crawl'​, ​'spider-sense'​]

If we wanted to define the types that are expected for each


key-value pair in the person object, we define the types through
the following syntax:

let​ person: {

​name​: ​string​;

age: ​number​;

activeAvenger: ​boolean​;

powers: ​string​[];
} = {

​name​: ​'Peter Parker'​,

​age​: ​20​,

​activeAvenger​: ​true​,

​powers​: [​'wall-crawl'​, ​'spider-sense'​]

At first glance, this might look really odd. After all, it looks like
you’re defining the object twice. In addition, you might have also
noticed that the type is not a traditional object because there
are semi-colons (i.e., ​;​) instead of commas (i.e., ​,​).

Limitations of Predefined
Types
While a lot of incredible things has and can be built with
pre-defined types provided to us by JavaScript and TypeScript,
the reality is that there leaves something left to be desired when
providing more descriptive types.

For example, what if we wanted to define a custom type that


only allowed developers to use a predefined set of button styles
(e.g., primary, secondary, error, disabled) on a variable?

Based on our current knowledge of TypeScript, we would start


with a variable that is typed to a string:

let​ buttonStyles: ​string​ = ​'primary'

However, while there is a type assigned to ​buttonStyles​, it


doesn’t do much as far as protecting developers from assigning
the wrong style type to it. This is where we need to take our
knowledge to the next level with defining custom types.

Let’s ReVue
In this lesson, we covered fundamental knowledge of taking the
basic types that most JavaScript developers are already familiar
with and applying them as types with TypeScript. In the next
lesson, we’ll be learning how to use one of TypeScript’s most
useful feature: the ability to define custom types.

Defining
Custom
Types
Introduction
As we saw in the last lesson, predefined types are incredibly
helpful for a number of common scenarios. However, as
applications grow in size and complexity with unique
requirements, it’s inevitable that there will be a need for custom
types. And when you want to define custom types in TypeScript,
there are two methods that account for most scenarios early on:
type​ and ​interface​.

What is ​ ? type​

In its simplest form, ​type​ allows you to define an alias that


refers to a specific way that the data should be shaped. For
example, in the last lesson, we were faced with a problem
where wanted to confine our ​buttonStyles​ variables to certain
CSS classes based on a design system.

let​ buttonStyles: ​string​ = ​'primary'

Given our knowledge at this point, the best we could do was


limit it to a string. However, through the use of ​type​, we can
accomplish our goal.
How to use ​type​?
Similar to declaring a variable, you use ​type​ as a declaration of
the variable type.

type​ buttonType = ​'primary'

In this starting example, we’ve declared a type called


buttonType​ that contains that value ​'primary'​. And similar to
standard type declaration, we can apply this type to our initial
example:

let​ buttonStyles: buttonType = ​'primary'

As it stands, our ​buttonStyles​ is a valid variable because it


matches our defined type. At this time, if someone tried to
switch the value of ​buttonStyles​ to:

let​ buttonStyles: buttonType = ​'secondary'


TypeScript would report an error, which is what we expect since
buttonType​ can only be a value of ​'primary'​ at this time. So
what if we need multiple values?

How to define multiple


values?
In the event that you need to allow a type to contain multiple
values, this is where the union operator comes in. The union
operator can be identified by a single pipe ​|​ and is most similar
to what we’re familiar with in JavaScript as ​||​ or in other words,
the “OR” operator.

With this knowledge, let’s continue enhancing our ​buttonType


example with the remaining valid button types.

type​ buttonType = ​'primary'​ | ​'secondary'​ | ​'success'​ |


'danger'

And now when we apply it, we can ensure that all ​buttonType
variables have the correct value!
// TypeScript will report an error because this doesn't
exist in the type!

const​ errorBtnStyles: buttonType = ​'error'

// This variable is type safe!

const​ dangerBtnStyles: buttonType = ​'danger'

Equipped with this new knowledge, we are now able to


complete our ​buttonStyles​ as intended.

What is an ​ interface​ ?
When getting started with ​interface​, the way I like to think
about it is a way to define a ​type​ for an object. To show this in
action, let’s take a look at an object we defined In the last
lesson.

let​ person: {

name​: ​string​;
age: ​number​;

activeAvenger: ​boolean​;

powers: ​string​[];

} = {

name​: ​'Peter Parker'​,

age​: ​20​,

activeAvenger​: ​true​,

powers​: [​'wall-crawl'​, ​'spider-sense'​]

As we can see here, even though the data isn’t too complicated,
it’s already fairly verbose and adds clutter to our code. Wouldn’t
it be nice if it could look something like this instead?

let​ person: Hero = {

name​: ​'Peter Parker'​,

age​: ​20​,

activeAvenger​: ​true​,
powers​: [​'wall-crawl'​, ​'spider-sense'​]

Well, with an ​interface​, you can totally do this!

How to define an ​interface​?


Just like a ​type​, you declare an ​interface​ by prefixing the
variable name with ​interface​. So using our hero example from
above, it would start out looking like this:

interface​ Hero = { }

Once we have this structure in place, then it’s only a matter of


defining our object types within the ​interface​.

interface​ Hero {

name​: ​string​;

age: ​number​;

activeAvenger: ​boolean​;
powers: ​string​[];

And just like that, we can now define variables that will be
checked against the type ​Hero​!

Can you use ​type​ in an


interface?
Let’s say we want to enhance our ​Hero​ interface by defining
what comic-book universe they live in. Well, without the use of
type​, it might look like this:

interface​ Hero {

name​: ​string​;

age: ​number​;

activeAvenger: ​boolean​;

powers: ​string​[];
universe: ​string​;

But like most applications, this isn’t useful given we want to


restrict the ​universe​ property to only contain ​Marvel​ or ​DC​ as a
value. Well, it looks like it’s time to call upon our power of ​type​!

type​ ComicUniverse = ​'Marvel'​ | ​'DC'

interface​ Hero {

name​: ​string​;

age: ​number​;

activeAvenger: ​boolean​;

powers: ​string​[];

universe: ComicUniverse;

}
And just like that, we’ve now combined our ​interface​ and ​type
together!

Let’s ReVue

In this lesson, you’ve learned how to define custom types using


the ​type​ and ​interface​ types. In addition, you’ve learned about
union operators as a way to add more flexibility to our custom
type definitions.

That said, I want to stress the importance that this lesson


equips you with what you need to get started with ​type​ and
interface​, but there is more to explore on your own if you
choose to. And while you will see lots of different opinions within
the TypeScript community as far as how they should be used,
my recommendation for your initial mental model is to use
interface​ for objects, and use ​type​ for everything else.

Thanks for watching, and I’ll see you in the next lesson.
Data with
Custom
Types
Introduction
Now that we know how to define custom types, it’s time for us to
apply our new abilities to Vue itself! To start, we will be taking a
look at the data option first.
The Problem
When we open up ​EventDetails.vue​, one of the problems that
might not be immediately obvious for those of us who haven’t
used types before is that currently our editor cannot provide
much help in terms of what is valid or not.

📄 EventDetails.vue
<​template​>

​<​div​ ​v-if​=​"event"​>

​<​h1​>​{{ event.title }}​</​h1​>

​<​p​>​{{ event.time }} on {{ event.date }} @ {{


event.location }}​</​p​>

​<​p​>​{{ event.description }}​</​p​>

​</​div​>

</​template​>
For example, while we reference ​event.id​, there’s nothing to
inform us that this is an attribute that exists. In fact, the only way
to really check is to wait until the code runs. And while this
might not seem like a huge impediment at first, this can cause a
lot of issues when the data becomes more complex.

Introducing VueDX
So how do we get our editor to help us? Well, the first step is to
install a new VS Code extension called ​VueDX​.

It is a new extension designed to enhance our developer


experience with Vue in a lot of different ways. We won’t have
time to cover everything in this lesson, but just know it’s worth
taking a closer look at!

Once we’ve installed it and restarted VS Code, one of the first


things you may notice is that our editor is now reporting errors.
This is great!
Now let’s fix these errors!

Defining the Event


Custom Type
The reason we are seeing this error is that as can see in our
component definition, ​events​ is actually defined as ​null​.

📄 EventDetails.vue
import​ { defineComponent } ​from​ ​'vue'

export​ ​default​ defineComponent({

​name​: ​'EventDetails'​,
data() {

​return​ {

​event​: ​null

},

})

And as far as types are concerned, ​null​ will never have a


property of ​title​, ​time​, etc. so the errors we are seeing make
sense.

So to fix this, let’s start by tracing back to how the Event type
should be structured. The way we do this is to trace our steps
back to where the data source is coming from, which is an API
call in ​EventService.ts

📄 EventService.vue
import​ axios ​from​ ​'axios'
const​ apiClient = axios.create({

​baseURL​:
'<https://my-json-server.typicode.com/Code-Pop/Real-World
_Vue-3>'​,

​withCredentials​: ​false​,

​headers​: {

​Accept​: ​'application/json'​,

​'Content-Type'​: ​'application/json'

})

When we visit the page, we can navigate the site to find


examples of our event. And what we end up with looks
something like this:

​"id"​: ​123​,

​"category"​: ​"animal welfare"​,


​"title"​: ​"Cat Adoption Day"​,

​"description"​: ​"Find your new feline friend at this


event."​,

​"location"​: ​"Meow Town"​,

​"date"​: ​"January 28, 2022"​,

​"time"​: ​"12:00"​,

​"organizer"​: ​"Kat Laydee"

With this, we can now say with confidence that ​events​ should
be an Array that contains a series of objects with the following
properties:

● id
● category
● title
● description
● location
● date
● time
● organizer

So, to get started, we’ll begin by defining a ​types.ts​ in the ​src


directory to centralize where types are defined in order to also
allow other files to import types as needed. And in here, we’ll go
ahead and define our ​EventItem​ type.

📄 types.ts
export​ ​interface​ EventItem {

​id​: ​number

​category​: ​string

​title​: ​string

​description​: ​string

​location​: ​string

​date​: ​string

​time​: ​string

​organizer​: ​string

}
To clarify, we could certainly dive deeper into refining the types
for the individual properties of ​EventItem​, but for this lesson, I’ve
kept it simple for now.

Now that we have defined ​EventItem​ and made it importable


with the ​export​ keyword, let’s import it into ​EventDetails.vue​.

📄 EventDetails.vue
import​ { defineComponent } ​from​ ​'vue'

import​ { EventItem } ​from​ ​'../types'

export​ ​default​ defineComponent({

​name​: ​'EventList'​,

data() {

​return​ {

​event​: ​null

},
})

At this point though, you might be wondering, “Well, how do I


add ​EventItem​ as a type to events?” And you’d be right,
because as it stands right now, we need to learn a new
technique before we can type our events property: type
assertions.

What are type


assertions?
At its core definition, type assertions allow you to override the
inferred type by the editor. In other words, it is a way for you to
tell the compiler that you know more about the type than it does.

For example, if we have a type called ​TodoItem​ and an empty


object:

interface​ TodoItem {
​label​: ​string

​complete​: ​boolean

const​ futureTodoItem = {}

futureTodoItem.label = ​'Install VueDX extension'

futureTodoItem.complete = ​false

TypeScript by default, will infer that ​futureTodoItem​ is simply an


empty object that should not have any properties in it and will
report errors stating as such. But we know it should be a
TodoItem​ type, so we can tell TypeScript this by using the ​as
keyword to override the default behavior.

interface​ TodoItem {

​label​: ​string

​complete​: ​boolean
}

const​ futureTodoItem = {} ​as​ TodoItem

futureTodoItem.label = ​'Install VueDX extension'

futureTodoItem.complete = ​false

And just like that, everything works now!

Use Type Assertion


to Define Types in
Data
Equipped with our new abilities, let’s fix our type in
EventDetails​ now!
📄 EventDetails.vue
import​ { defineComponent } ​from​ ​'vue'

import​ { EventItem } ​from​ ​'../types'

export​ ​default​ defineComponent({

​name​: ​'EventDetails'​,

data() {

​return​ {

​event​: ​null

},

})

We know that ​event​ should be an object, so we can start by


switching out ​null​ for ​{}​.

📄 EventDetails.vue
import​ { defineComponent } ​from​ ​'vue'

import​ { EventItem } ​from​ ​'../types'

export​ ​default​ defineComponent({

​name​: ​'EventDetails'​,

data() {

​return​ {

​event​: {}

},

})

But we need to go one step further since we want to tell


TypeScript that it is an object of ​EventItem​ type. And we can
accomplish this with our new ​as​ keyword!

📄 EventDetails.vue
import​ { defineComponent } ​from​ ​'vue'
import​ { EventItem } ​from​ ​'../types'

export​ ​default​ defineComponent({

​name​: ​'EventDetails'​,

data() {

​return​ {

​event​: {} ​as​ EventItem

},

})

And just like that, we’ve now properly typed our ​data​ property!

Code Challenge
Now that you know how to add custom types to the data option,
checkout the ​06-challenge​ branch and define the correct
custom type for ​EventList.vue​.

Let’s ReVue
In this lesson, we’ve learned about the new VueDX extension,
what type assertions are, and how to use the new ​as​ keyword in
order to define custom types in our data option in Vue 3.

In the next lesson, we’ll be taking a look at how to define props


with custom types. See you there!
Props with
Types
Introduction
In the last lesson, we learned how to apply custom types to our
data option. So in this lesson, we’re going to continue our
journey with TypeScript and Vue 3 by learning how to apply
custom types to props. And if you want to follow along, be sure
to check out branch ​07-begin​.

Default Prop Types


When we open up ​EventDetails.vue​, you may have noticed
that we have no prop typing on this.

import​ { defineComponent } ​from​ ​'vue'

export​ ​default​ defineComponent({

​props​: [​'id'​]

})

Using standard Vue prop types, we can define this better by


using the Object syntax in order to other developers know that
the ​id​ should be of type ​Number​ and that it is required.

import​ { defineComponent } ​from​ ​'vue'

export​ ​default​ defineComponent({

​props​: {

​id​: {

​type​: ​Number​,
​required​: ​true

},

})

This is great and is useful for a lot of scenarios, but when we


look at our ​EventCard​ component, we’ll see that we have a prop
type applied. That said, while it’s useful to know it should be an
Object​, it’d be much more informative if we can tell developers
how the object is defined.

Applying a Custom
Type to Props
Your first instinct from applying custom types to our data
property is probably to swap out ​Object​ for our ​EventItem​ type,
but this won’t work. After all, ​EventItem​ is a TypeScript type —
which means it is TypeScript specific — and not something that
JavaScript itself can evaluate.

Your next thought might be to use our new ​as​ keyword to assert
the type, and you would be close, but this won’t work either.
The reason for this is because with props in Vue, Vue provides
additional functionality to check props that go beyond
TypeScript. Before we can dive into this, we need to first learn
about TypeScript generics.

TypeScript Generics
At this point, we have learned how to type basic functions in
TypeScript.

function​ createList(item: ​number​): number[] {

​const​ newList: ​number​[] = []

newList.push(item)

​return​ newList
}

const​ numberList = createList(​123​)

With this, you get type safety, but the function is rather limiting
isn’t it? And if we were to rename it properly, we’d probably
want to call it ​addNumberToNumberList​, but this wouldn’t be very
reusable then. So the question is, how would we make this
more reusable?

In TypeScript, this is solved with the concept of “Generics.” At a


high level, they allow you to define a dynamic type that is
reused in the function later on. The key marker that generics
are being used is when a function is appended with the ​<>
bracket, which allows you to pass in a type rather than a
JavaScript value that is passed in parentheses instead. So the
code we had before would become:

function​ createList<CustomType>(item: CustomType):


CustomType[] {

​const​ newList: CustomType[] = []


newList.push(item)

​return​ newList

const​ numberList = createList<​number​>(​123​)

Before we move on though, I do want to note that even though


it’s incredibly confusing to new users and can make for difficult
to read code, it is a convention in the TypeScript community to
use single letter variables — starting with T — when defining
custom types in generics.

So out in other code bases, the same code above would look
like this:

function​ createList<T>(item: T): T[] {

​const​ newList: T[] = []


newList.push(item)

​return​ newList

const​ stringList = createList<T>(​123​)

Now that we know this, it’s time to get a little help from Vue to
finish our task!

PropType Helper Method


In Vue 3, when we want to apply custom types to props, we
need the built-in helper method called ​PropTypes​. You can use it
by importing it from Vue directly.

import​ { PropTypes } ​from​ ​'vue'


And while this may look straightforward at first. The reason we
needed to learn about TypeScript generics is because Vue 3’s
PropType​ helper method utilizes generics in order order to
provide us the benefits of both prop validation and custom
types.

Using our new syntax, we can update our ​EventCard.vue​ to the


following:

import​ { defineComponent, PropType } ​from​ ​'vue'

import​ { EventItem } ​from​ ​'../types'

export​ ​default​ defineComponent({

​props​: {

​event​: {

​type​: ​Object​ ​as​ PropType<EventItem>,

required: ​true

},
})

So if you think about it another way, this is how we tell Vue that
the prop value should be a type of ​PropType​ and ​EventItem​. And
just like that, you’ve now successfully added a custom type to
your prop!

Let’s Revue
In this lesson, we learned about the PropType helper method,
the concept of generics, and how we combine them with type
assertion to apply custom types to our props.

Computed &
Methods
with Custom
Types
As we begin to wrap up our journey together with the
fundamentals of using TypeScript with Vue 3, it’s time we
learned how to add custom types to computed properties and
methods.

Custom Types with


Computed
Properties
Let’s start with computed properties.

Here we have a standard single file component where the


language in the script block is marked for TypeScript.

<​script​ ​lang​=​"ts"​>

import​ { defineComponent } ​from​ ​'vue'

import​ { EventItem } ​from​ ​'../types'

export​ ​default​ defineComponent({

data() {

​return​ {

​events​: []

},

​methods​: {

secondEvent() {

​return​ ​this​.events[​1​]
}

})

</​script​>

We’re importing our ​defineComponent​ helper method from vue


as well as a custom ​EventItem​ type that you should recognize
from previous lessons.

Using what we learned in Data with Custom Types, we know


that we can type our events array by using the keyword ​as​ to
tell TypeScript that it is an array of ​EventItem​s.

<​script​ ​lang​=​"ts"​>

import​ { defineComponent } ​from​ ​'vue'

import​ { EventItem } ​from​ ​'../types'

export​ ​default​ defineComponent({

data() {
​return​ {

​events​: [] ​as​ EventItem

},

​methods​: {

secondEvent(): EventItem {

​return​ ​this​.events[​1​]

})

</​script​>

But what about our computed property?

When it comes to computed properties, the key thing to


remember is that you want to focus on what the computed
property is returning. In other words, using our example, we
need to define what type that ​secondEvent​ will end up returning.
To do this, we use the syntax of the ​:​ and the following it with
the custom type the function should return:

<​script​ ​lang​=​"ts"​>

import​ { defineComponent } ​from​ ​'vue'

import​ { EventItem } ​from​ ​'../types'

export​ ​default​ defineComponent({

data() {

​return​ {

​events​: [] ​as​ EventItem

},

​methods​: {

secondEvent(): EventItem {

​return​ ​this​.events[​1​]

}
}

})

</​script​>

Believe it or not, just like that, you’ve successfully added a


custom type to your computed property!

Custom Types with


Methods
Now let’s shift gears to how we add custom types to methods.
Let’s start again with our component with a small change where
we have an ​addEvent​ method.

<​script​ ​lang​=​"ts"​>

import​ { defineComponent } ​from​ ​'vue'

import​ { EventItem } ​from​ ​'../types'


export​ ​default​ defineComponent({

data() {

​return​ {

​events​: [] ​as​ EventItem[]

},

​methods​: {

addEvent(newEvent) {

​this​.events.push(newEvent)

})

</​script​>
In our ​addEvent​ function, we can see that it takes in a parameter
of ​newEvent​ and adds it to the ​events​ data that we’re tracking
inside of ​data()​.

When it comes to adding custom types to methods, there are


two key things to keep in mind:

1. Do we need to add types to the parameters being passed


into the method?
2. Do we need to add types to whatever is being returned by
the method?

Adding Custom Types to the


Parameter of a Method
In this particular function, our focus is on add a custom type to
the parameter ​newEvent​, which should a type of ​EventItem​. And
we can accomplish this by using the ​:​ syntax:

addEvent(newEvent: EventItem) {

​this​.events.push(newEvent)

}
Adding Custom Types to the
Return Value of a Method
For this scenario, let’s change up the method to fetching the
second event:

secondEvent() {

​return​ ​this​.events[​1​]

If you’re thinking this looks similar to the computed properties


example we saw earlier, you’d be correct! The solution to typing
your method’s return value is exactly the same.

secondEvent(): EventItem {

​return​ ​this​.events[​1​]
}

And with that, you know all you need to add custom types to
your methods!

Let’s ReVue
In this lesson, we’ve learned how to apply custom types to
computed properties by focusing on what is being returned. And
finally, for methods, we need to make sure we check whether
our parameters and return value need any custom types.

That’s it for this lesson. See you in the next one!

You might also like