# TypeScript Support
Vue CLI (opens new window) provides built-in TypeScript tooling support.
# Official Declaration in NPM Packages
A static type system can help prevent many potential runtime errors as applications grow, which is why Vue 3 is written in TypeScript. This means you don't need any additional tooling to use TypeScript with Vue - it has first-class citizen support.
# Recommended Configuration
// tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
// this enables stricter inference for data properties on `this`
"strict": true,
"jsx": "preserve",
"moduleResolution": "node"
}
}
2
3
4
5
6
7
8
9
10
11
Note that you have to include strict: true
(or at least noImplicitThis: true
which is a part of strict
flag) to leverage type checking of this
in component methods otherwise it is always treated as any
type.
See TypeScript compiler options docs (opens new window) for more details.
# Webpack Configuration
If you are using a custom Webpack configuration ts-loader
needs to be configured to parse <script lang="ts">
blocks in .vue
files:
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/],
},
exclude: /node_modules/,
},
{
test: /\.vue$/,
loader: 'vue-loader',
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Development Tooling
# Project Creation
Vue CLI (opens new window) can generate new projects that use TypeScript. To get started:
# 1. Install Vue CLI, if it's not already installed
npm install --global @vue/cli
# 2. Create a new project, then choose the "Manually select features" option
vue create my-project-name
# If you already have a Vue CLI project without TypeScript, please add a proper Vue CLI plugin:
vue add typescript
2
3
4
5
6
7
8
Make sure that script
part of the component has TypeScript set as a language:
<script lang="ts">
...
</script>
2
3
Or, if you want to combine TypeScript with a JSX render
function:
<script lang="tsx">
...
</script>
2
3
# Editor Support
For developing Vue applications with TypeScript, we strongly recommend using Visual Studio Code (opens new window), which provides great out-of-the-box support for TypeScript. If you are using single-file components (SFCs), get the awesome Vetur extension (opens new window), which provides TypeScript inference inside SFCs and many other great features.
WebStorm (opens new window) also provides out-of-the-box support for both TypeScript and Vue.
# Defining Vue components
To let TypeScript properly infer types inside Vue component options, you need to define components with defineComponent
global method:
import { defineComponent } from 'vue'
const Component = defineComponent({
// type inference enabled
})
2
3
4
5
# Using with Options API
TypeScript should be able to infer most of the types without defining types explicitly. For example, if you have a component with a number count
property, you will have an error if you try to call a string-specific method on it:
const Component = defineComponent({
data() {
return {
count: 0
}
},
mounted() {
const result = this.count.split('') // => Property 'split' does not exist on type 'number'
}
})
2
3
4
5
6
7
8
9
10
If you have a complex type or interface, you can cast it using type assertion (opens new window):
interface Book {
title: string
author: string
year: number
}
const Component = defineComponent({
data() {
return {
book: {
title: 'Vue 3 Guide',
author: 'Vue Team',
year: 2020
} as Book
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Annotating Return Types
Because of the circular nature of Vue’s declaration files, TypeScript may have difficulties inferring the types of computed. For this reason, you may need to annotate the return type of computed properties.
import { defineComponent } from 'vue'
const Component = defineComponent({
data() {
return {
message: 'Hello!'
}
},
computed: {
// needs an annotation
greeting(): string {
return this.message + '!'
}
// in a computed with a setter, getter needs to be annotated
greetingUppercased: {
get(): string {
return this.greeting.toUpperCase();
},
set(newValue: string) {
this.message = newValue.toUpperCase();
},
},
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Annotating Props
Vue does a runtime validation on props with a type
defined. To provide these types to TypeScript, we need to cast the constructor with PropType
:
import { defineComponent, PropType } from 'vue'
interface Book {
title: string
author: string
year: number
}
const Component = defineComponent({
props: {
name: String,
success: { type: String },
callback: {
type: Function as PropType<() => void>
},
book: {
type: Object as PropType<Book>,
required: true
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
WARNING
Because of a design limitation (opens new window) in TypeScript when it comes
to type inference of function expressions, you have to be careful with validators
and default
values for objects and arrays:
import { defineComponent, PropType } from 'vue'
interface Book {
title: string
year?: number
}
const Component = defineComponent({
props: {
bookA: {
type: Object as PropType<Book>,
// Make sure to use arrow functions
default: () => ({
title: 'Arrow Function Expression'
}),
validator: (book: Book) => !!book.title
},
bookB: {
type: Object as PropType<Book>,
// Or provide an explicit this parameter
default(this: void) {
return {
title: 'Function Expression'
}
},
validator(this: void, book: Book) {
return !!book.title
}
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Annotating emits
We can annotate a payload for the emitted event. Also, all non-declared emitted events will throw a type error when called:
const Component = defineComponent({
emits: {
addBook(payload: { bookName: string }) {
// perform runtime validation
return payload.bookName.length > 0
}
},
methods: {
onSubmit() {
this.$emit('addBook', {
bookName: 123 // Type error!
})
this.$emit('non-declared-event') // Type error!
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Using with Composition API
On setup()
function, you don't need to pass a typing to props
parameter as it will infer types from props
component option.
import { defineComponent } from 'vue'
const Component = defineComponent({
props: {
message: {
type: String,
required: true
}
},
setup(props) {
const result = props.message.split('') // correct, 'message' is typed as a string
const filtered = props.message.filter(p => p.value) // an error will be thrown: Property 'filter' does not exist on type 'string'
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Typing refs
Refs infer the type from the initial value:
import { defineComponent, ref } from 'vue'
const Component = defineComponent({
setup() {
const year = ref(2020)
const result = year.value.split('') // => Property 'split' does not exist on type 'number'
}
})
2
3
4
5
6
7
8
9
Sometimes we may need to specify complex types for a ref's inner value. We can do that by simply passing a generic argument when calling ref to override the default inference:
const year = ref<string | number>('2020') // year's type: Ref<string | number>
year.value = 2020 // ok!
2
3
Note
If the type of the generic is unknown, it's recommended to cast ref
to Ref<T>
.
# Typing reactive
When typing a reactive
property, we can use interfaces:
import { defineComponent, reactive } from 'vue'
interface Book {
title: string
year?: number
}
export default defineComponent({
name: 'HelloWorld',
setup() {
const book = reactive<Book>({ title: 'Vue 3 Guide' })
// or
const book: Book = reactive({ title: 'Vue 3 Guide' })
// or
const book = reactive({ title: 'Vue 3 Guide' }) as Book
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Typing computed
Computed values will automatically infer the type from returned value
import { defineComponent, ref, computed } from 'vue'
export default defineComponent({
name: 'CounterButton',
setup() {
let count = ref(0)
// read-only
const doubleCount = computed(() => count.value * 2)
const result = doubleCount.value.split('') // => Property 'split' does not exist on type 'number'
}
})
2
3
4
5
6
7
8
9
10
11
12
13