SoFunction
Updated on 2025-03-10

Detailed explanation of the TS type instance for the Vue3 component

Annotate the type for props

If you want to talk about the most popular front-end technologies this year, Vue3 and TS are definitely on the list. It is understood that many companies are already using Vue3 + TS + Vite to develop new projects. Then we can't fall behind. Today I will share with you how to use the TS type in Vue3 components in combination with Composition-Api. If you have friends who don’t know or are not familiar with it, let’s learn together!

Using <script setup>

When using <script setup>, the defineProps() macro function supports deriving types from its parameters:

<script setup lang="ts">
const props = defineProps({
  foo: { type: String, required: true },
  bar: Number
})
 // string
 // number | undefined
</script>

This is called a runtime declaration, because the parameters passed to defineProps() are used as the runtime props option.

The second method is to define the type of props through generic parameters, which is more direct:

<script setup lang="ts">
const props = defineProps<{
  foo: string
  bar?: number
}>()
</script>

This is called a type-based declaration, and the compiler tries to deduce equivalent runtime options based on type parameters as much as possible.

We can also move the props type into a separate interface:

<script setup lang="ts">
interface Props {
  foo: string
  bar?: number
}
const props = defineProps<Props>()
</script>

The type-based approach is more concise, but loses the ability to define props default values. We can solve it through the current experimental responsive syntax sugar:

&lt;script setup lang="ts"&gt;
interface Props {
  foo: string
  bar?: number
}
// Responsive syntax sugar The default value will be compiled into equivalent runtime optionsconst { foo, bar = 100 } = defineProps&lt;Props&gt;()
&lt;/script&gt;

This behavior currently needs to be explicitly selected in the configuration:

// 
export default {
  plugins: [
    vue({
      reactivityTransform: true
    })
  ]
}
// 
 = {
  chainWebpack: (config) => {
    
      .rule('vue')
      .use('vue-loader')
      .tap((options) => {
        return {
          ...options,
          reactivityTransform: true
        }
      })
  }
}

Non <script setup>

If <script setup> is not used, defineComponent() must be used in order to enable props type derivation. The props object type passed to setup() is derived from the props option.

import { defineComponent } from 'vue'
export default defineComponent({
  props: {
    message: String
  },
  setup(props) {
     // <-- Type: string  }
})

Annotate the type for emits

Using <script setup>

In <script setup>, the type annotation of the emit function can also be used with runtime declarations or type-based declarations:

&lt;script setup lang="ts"&gt;
// Runtimeconst emit = defineEmits(['change', 'update'])
// Based on typeconst emit = defineEmits&lt;{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}&gt;()
&lt;/script&gt;

We can see that type-based declarations can allow us to have a finer granular control over the types of the triggered events.

Non <script setup>

If <script setup> is not used, defineComponent() can also deduce the type of emit function exposed to the setup context based on the emits option:

import { defineComponent } from 'vue'
export default defineComponent({
  emits: ['change'],
  setup(props, { emit }) {
    emit('change') // <-- Type check / Automatic completion  }
})

Annotate the type for ref()

Default derivation type

ref will automatically deduce its type based on the initialization value:

import { ref } from 'vue'
// Derived type: Ref<number>const year = ref(2020)
// =&gt; TS Error: Type 'string' is not assignable to type 'number'.
 = '2020'

Specify the type through the interface

Sometimes we might want to specify a more complex type for the values ​​within ref, which can be done by using the Ref interface:

import { ref } from 'vue'
import type { Ref } from 'vue'
const year: Ref&lt;string | number&gt; = ref('2020')
 = 2020 // success!

Specify type by generics

Alternatively, pass a generic parameter when calling ref() to override the default derivation behavior:

// The type obtained: Ref<string | number>const year = ref&lt;string | number&gt;('2020')
 = 2020 // success!

If you specify a generic parameter but don't give the initial value, the final result will be a undefined union type:

// The derived type: Ref<number | undefined>const n = ref&lt;number&gt;()

Annotate the type for reactive()

Default derivation type

reactive() also implicitly deduces types from its parameters:

import { reactive } from 'vue'
// The derived type: { title: string }const book = reactive({ title: 'Vue 3 Guide' })

Specify the type through the interface

To explicitly specify the type of a reactive variable, we can use the interface:

import { reactive } from 'vue'
interface Book {
  title: string
  year?: number
}
const book: Book = reactive({ title: 'Vue 3 Guide' })

Annotate the type for computered()

Default derivation type

computed() automatically deduces the type from the return value of its computed function:

import { ref, computed } from 'vue'
const count = ref(0)
// The derived type: ComputedRef<number>const double = computed(() =&gt;  * 2)
// =&gt; TS Error: Property 'split' does not exist on type 'number'
const result = ('')

Specify type by generics

You can also explicitly specify the type through generic parameters:

const double = computed&lt;number&gt;(() =&gt; {
  // If the return value is not the number type, an error will be reported})

Annotate the type of event handler function

When handling native DOM events, the parameters of the event handler function should be correctly marked with the type. Let's take a look at this example:

&lt;script setup lang="ts"&gt;
function handleChange(event) {
  // `event` is implicitly marked as `any` type  ()
}
&lt;/script&gt;
&lt;template&gt;
  &lt;input type="text" @change="handleChange" /&gt;
&lt;/template&gt;

When there is no type annotation, this event parameter is implicitly labeled as any type. This also configures "strict": true or "noImplicitAny": true A TS error is reported in . Therefore, it is recommended to explicitly label the parameters of the event handler function. Additionally, you may need to explicitly cast the properties on the event:

function handleChange(event: Event) {
  (( as HTMLInputElement).value)
}

Annotate the type for provide / inject

Provide and inject usually run in different components. To correctly tag types for injected values, Vue provides an InjectionKey interface, a generic type inherited from Symbol, which can be used to synchronize the injected value between providers and consumers:

import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'
const key = Symbol() as InjectionKey&lt;string&gt;
provide(key, 'foo') // If the provided non-string value will cause an errorconst foo = inject(key) // Type of foo: string | undefined

It is recommended to place the type of injected key in a separate file so that it can be imported by multiple components.

When using string injection key, the type of injected value is unknown, which needs to be explicitly declared through generic parameters:

const foo = inject&lt;string&gt;('key') // Type: string | undefined

Note that the injected value can still be undefined, because there is no guarantee that the provider will provide this value at runtime. When a default value is provided, the undefined type can be removed:

const foo = inject&lt;string&gt;('foo', 'bar') // Type: string

If you are sure that the value will always be provided, you can also cast the value:

const foo = inject('foo') as string

Refer to the tag type for the dom template

The template ref needs to be created with an explicitly specified generic parameter and an initial value null:

<script setup lang="ts">
import { ref, onMounted } from 'vue'
const el = ref<HTMLInputElement | null>(null)
onMounted(() => {
  ?.focus()
})
</script>
<template>
  <input ref="el" />
</template>

Note For strict type safety, it is necessary to use optional chains or type guards when accessing. This is because the value of this ref is initial null until the component is mounted, and v-if is also set to null when uninstalling the referenced element.

Reference annotation type for component templates

Sometimes, we need to add a template ref to a child component so that the method it exposes is called. For example, we have a MyModal subcomponent that has a method to open a modal box:

<!--  -->
<script setup lang="ts">
import { ref } from 'vue'
const isContentShown = ref(false)
const open = () => ( = true)
defineExpose({
  open
})
</script>

In order to get the type of MyModal, we first need to obtain its type through typeof, and then use the InstanceType tool type built in TypeScript to get its instance type:

<!--  -->
<script setup lang="ts">
import MyModal from './'
const modal = ref<InstanceType<typeof MyModal> | null>(null)
const openModal = () => {
  ?.open()
}
</script>

Ok, the above is the basic method of using the TS type in Vue3 components, and it is also my recent Vue3 study notes. Welcome to exchange and discuss in the comment area and learn and grow together.

If it helps you, don’t forget to like and support it! 🌹🌹

Reference documentation:

/guide/intro…

/guide/intro…

The above is a detailed explanation of the examples for the Vue3 component annotation TS type. For more information about the Vue3 component annotation TS type, please follow my other related articles!