Reactive Variables With Type Inference - TypeScript Friendly Vue 3 - Vue Mastery
Reactive Variables With Type Inference - TypeScript Friendly Vue 3 - Vue Mastery
In this lesson, we’ll start using TypeScript with ref() and reactive(). Type inference is a main theme
when working with reactive variables. We’ll go through some general patterns and caveats on
creating reactivie variables with/without type inference.
📃 /src/App.vue
</script>
Everything about ref and reactive is still the same, including type inference.
The code that we have here looks just like plain old JavaScript because the type of this variable
is inferred.
Type inference is a huge part of using TypeScript. It will guess the type of the variable by just
looking at the value you pass into the ref function. So even if you don’t specify the variable’s
type, your IDE will still be able to show you the type info when you put your cursor over the
variable.
Just like ref() , we can still use reactive() the same way as in the original Composition API
syntax:
📃 /src/App.vue
The inferred type for appInfo will be { name: string, slogan: string } .
Template
Let’s render all of our variables in the template:
📃 /src/App.vue
<template>
<div>
<h1>{{ appInfo.name }}</h1>
<h2>{{ appInfo.slogan }}</h2>
</div>
<p>{{ count }}</p>
</template>
Even with the script setup syntax, we’re still writing template code the same way. Again, this is
because the script setup syntax is still the Composition API, just under a different skin.
One subtle difference is that we didn’t have to return the appInfo and count from
the setup() function because our code is not inside a function anymore.
Put your cursor on the count variable in the template, Volar will show you that its type
is number not Ref<number> .
That’s because the template automatically “unpacks” the value of the Ref object.
📃 /src/App.vue
</script>
<template>
<div>
<h1>{{ appInfo.name }}</h1>
<h2>{{ appInfo.slogan }}</h2>
</div>
<p>{{ count }}</p>
</template>
Because of type inference, the code in this section looks just like JavaScript. (although it’s really
TypeScript)
Next, we’ll write some TypeScript code that actually looks like TypeScript.
Putting your cursor on the variable will show you its type:
If { name: string; slogan: string; } is too verbose for you, you might want to create
a custom type for this:
📃 /src/App.vue
interface AppInfo {
name: string
slogan: string
}
All this extra setup makes our code more readable and maintainable. When you look at this code,
you know immediately that the variable is meant to be an AppInfo type object.
Additionally, when you put your cursor on the variable, it will tell you that the variable appInfo is
of type AppInfo .
Although they mean the same thing, AppInfo is so much cleaner than { name: string;
slogan: string; } .
Ref annotation
But manually specifying the type of a variable is not always so helpful.
For example, we clearly see that the following variable is meant to be a Ref of number :
Adding the extra annotation would actually make the code look bloated:
However, there is a common case where type inference is not able to figure out the type we
intended.
For example, it’s common in a real app to fetch the initial value from an HTTP service
via onMounted . So when we first declare the ref, we have to set its value to null :
📃 /src/App.vue
// CHANGE
const count = ref(null)
// NEW
onMounted(() => {
fetchCount((initialCount) => {
count.value = initialCount
})
})
This code wouldn’t work in TypeScript because our count variable has the
type Ref<null> (guessed by TypeScript’s type inference). That means this Ref object can
only be set with null as its value. Setting it to initialCount , which is a number , will make
TypeScript scream at you.
To solve this problem, we have to specify our intended type ( number | null ) through the
generic argument of ref() :
📃 /src/App.vue
// CHANGE
const count = ref<number | null>(null)
onMounted(() => {
fetchCount((initialCount) => {
count.value = initialCount
})
})
We are basically telling TypeScript that this Ref object can be set with either
a number or null as its value .
As a less common alternative, we can also specify the type info of this ref on the left side of the
assignment:
But we would have to import the Ref type, which is more typing to get the same result. I’m
showing this option here so you know that it’s possible to do it this way, but it’s just less intuitive.
📃 /src/App.vue
interface AppInfo {
name: string
slogan: string
}
onMounted(() => {
fetchCount((initialCount) => {
count.value = initialCount
})
})
<script>
<template>
<div>
<h1>{{ appInfo.name }}</h1>
<h2>{{ appInfo.slogan }}</h2>
</div>
<p>{{ count }}</p>
</template>
The general principle when working with TypeScript is to let type inference do its work, and only
step in when type inference is not enough.
Aside from the use cases that we’ve gone through, there are more places where we would need to
manually specify the type info, such as in functions.