Vue3 component TS type declaration

To say that this year's hottest front-end technology, 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 TS type in combination with Composition-Api in Vue3 components. If there are friends who don’t know or are not familiar with it, let’s learn together!

Annotate types for props

use setup

when using

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

props.foo // string
props.bar // number | undefined
</script>

This is called a runtime declaration because the arguments passed to defineProps() are used as props options at runtime.

The second way 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 known as a type-based declaration, and the compiler tries to deduce equivalent runtime options based on the type parameters as much as possible.

We could also move the type of props 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 default values ​​for props. We can solve this with the currently experimental responsive syntactic sugar:

<script setup lang="ts">
interface Props {
    
    
  foo: string
  bar?: number
}

// Responsive syntactic sugar defaults will be compiled into equivalent runtime options
const { foo, bar = 100 } = defineProps()

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

// vite.config.js
export default {
    
    
  plugins: [
    vue({
    
    
      reactivityTransform: true
    })
  ]
}

// vue.config.js
module.exports = {
    
    
  chainWebpack: (config) => {
    
    
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap((options) => {
    
    
        return {
    
    
          ...options,
          reactivityTransform: true
        }
      })
  }
}

setup

if not used

import {
    
     defineComponent } from 'vue'

export default defineComponent({
    
    
  props: {
    
    
    message: String
  },
  setup(props) {
    
    
    props.message // <-- 类型:string
  }
})

Annotate type for emits

setup

exist

<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])

// 基于类型
const emit = defineEmits<{
    
    
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

We can see that type-based declarations give us more fine-grained control over the types of events that are fired.

non-setup

if not used

import {
    
     defineComponent } from 'vue'

export default defineComponent({
    
    
  emits: ['change'],
  setup(props, {
     
      emit }) {
    
    
    emit('change') // <-- 类型检查 / 自动补全
  }
})

Annotate the type for ref()

default derivation type

A ref automatically deduces its type from the value it was initialized with:

import {
    
     ref } from 'vue'

// 推导出的类型:Ref<number>
const year = ref(2020)

// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'

specifying a type through an interface

Sometimes we may want to specify a more complex type for the value in ref, by using the Ref interface:

import {
    
     ref } from 'vue'
import type {
    
     Ref } from 'vue'

const year: Ref<string | number> = ref('2020')

year.value = 2020 // 成功!

Specifying types through generics

Alternatively, pass a generic argument to ref() to override the default deduction behavior:

// 得到的类型:Ref<string | number>
const year = ref<string | number>('2020')

year.value = 2020 // 成功!

If you specify a generic parameter without an initial value, you end up with a union type containing undefined:

// 推导得到的类型:Ref<number | undefined>
const n = ref<number>()

Annotate type for reactive()

default derivation type

reactive() also implicitly deduces types from its arguments:

import {
    
     reactive } from 'vue'

// 推导得到的类型:{ title: string }
const book = reactive({
    
     title: 'Vue 3 指引' })

specifying a type through an interface

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

import {
    
     reactive } from 'vue'

interface Book {
    
    
  title: string
  year?: number
}

const book: Book = reactive({
    
     title: 'Vue 3 指引' })

Annotate the type for computed()

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)

// 推导得到的类型:ComputedRef<number>
const double = computed(() => count.value * 2)

// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')

Specifying types through generics

You can also explicitly specify the type via a generic parameter:

const double = computed<number>(() => {
    
    
  // 若返回值不是 number 类型则会报错
})

Annotate types for event handlers

When dealing with native DOM events, the parameters of the event handler should be correctly marked with types. Let's look at this example:

<script setup lang="ts">
function handleChange(event) {
    
    
  // `event` 隐式地标注为 `any` 类型
  console.log(event.target.value)
}
</script>

<template>
  <input type="text" @change="handleChange" />
</template>

When there is no type annotation, the event parameter will be implicitly marked as any type. This will also throw a TS error if "strict": true or "noImplicitAny": true is configured in tsconfig.json. Therefore, it is recommended to explicitly type the parameters of event handler functions. Additionally, you may need to explicitly cast properties on the event:

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

Annotate type for provide / inject

provide and inject will usually run in different components. To correctly mark the type of the injected value, Vue provides an InjectionKey interface, which is a generic type inherited from Symbol, which can be used to synchronize the type of injected value between the provider and the consumer:

import {
    
     provide, inject } from 'vue'
import type {
    
     InjectionKey } from 'vue'

const key = Symbol() as InjectionKey<string>

provide(key, 'foo') // 若提供的是非字符串值会导致错误

const foo = inject(key) // foo 的类型:string | undefined

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

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

const foo = inject<string>('key') // 类型: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<string>('foo', 'bar') // 类型:string

You can also cast the value if you are sure the value will always be provided:

const foo = inject('foo') as string

#Annotation type for dom template reference
Template ref needs to be created with an explicitly specified generic parameter and an initial value of null:

<script setup lang="ts">
import {
    
     ref, onMounted } from 'vue'

const el = ref<HTMLInputElement | null>(null)

onMounted(() => {
    
    
  el.value?.focus()
})
</script>

<template>
  <input ref="el" />
</template>

Note that for strict type safety it is necessary to use optional chaining or type guards when accessing el.value. This is because until the component is mounted, the value of this ref is initially null, and v-if will also be set to null when the referenced element is unmounted.

#Refer to the annotation type for the component template
Sometimes, we need to add a template ref to a subcomponent in order to call the method it exposes. For example, we have a MyModal subcomponent that has a method to open a modal:

<!-- MyModal.vue -->
<script setup lang="ts">
import {
    
     ref } from 'vue'

const isContentShown = ref(false)
const open = () => (isContentShown.value = true)

defineExpose({
    
    
  open
})
</script>

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

<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'

const modal = ref<InstanceType<typeof MyModal> | null>(null)

const openModal = () => {
    
    
  modal.value?.open()
}
</script>

Supongo que te gusta

Origin blog.csdn.net/weixin_44064067/article/details/127067031
Recomendado
Clasificación