TypeScript and Composition APIs

TypeScript and Composition APIs

1. Mark the props type of the component

1.1 use<script setup>

A macro function supports inferring types from its arguments <script setup>when using :defineProps()

<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 will be used as options defineProps()at runtime .props

However, it is usually more straightforward to define the type of props via generic parameters:

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

This is called "type-based declaration". Whenever possible, the compiler tries to deduce equivalent runtime options from the type parameters. In this scenario, the runtime options compiled in our second example are exactly the same as the first one.

Either type-based declarations or runtime declarations can be used, but not both.

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>

grammatical restrictions

In order to generate correct runtime code, defineProps()the generic parameter passed to must be one of the following:

  • A type literal:
defineProps<{
      
       /*... */ }>()
  • A reference to an interface or object type literal in the same file :
interface Props {
    
    /* ... */}

defineProps<Props>()

Interface or object literal types can contain type references imported from other files, however, a definePropsgeneric parameter passed to cannot itself be an imported type:

import {
    
     Props } from './other-file'

// 不支持!
defineProps<Props>()

This is because Vue components are compiled separately and the compiler currently does not grab imported files to analyze source types. We plan to address this limitation in a future release.

1.2 Props deconstruct default value

When using type-based declarations, we lose the ability to define default values ​​for props. This can be solved with the currently experimental responsive syntactic sugar :

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

// 对 defineProps() 的响应性解构
// 默认值会被编译为等价的运行时选项
const {
    
     foo, bar = 100 } = defineProps<Props>()
</script>

This behavior currently requires an explicit opt-in .

1.3 <script setup>In non-scenes

If not used <script setup>, must be used in order to enable type inference for props defineComponent(). setup()The type of the props object passed to is propsdeduced from the options.

import {
    
     defineComponent } from 'vue'

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

2. Label the emits type of the component

In <script setup>, emitthe type annotation of a function can also be done through a runtime declaration or a type declaration:

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

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

The type parameter should be a type literal with a call signature . The type of this type literal is emitthe type of the returned function. We can see that type-based declarations give us more fine-grained control over the types of events that are fired.

If not used <script setup>, the type of the function exposed in the setup context defineComponent()can also be deduced from the option :emitsemit

import {
    
     defineComponent } from 'vue'

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

Three, for ref()the label type

A ref will deduce its type from its initial value:

import {
    
     ref } from 'vue'

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

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

Sometimes we may want to specify a more complex type for the value inside the ref, by using Refthis type:

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

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

year.value = 2020 // 成功!

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

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

year.value = 2020 // 成功!

If you specify a generic parameter without an initial value, the end result will be a undefinedunion type containing :

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

4. reactive()Label the type

reactive()Also implicitly deduces types from its arguments:

import {
    
     reactive } from 'vue'

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

To explicitly annotate reactivethe type of a variable, we can use interfaces:

import {
    
     reactive } from 'vue'

interface Book {
    
    
  title: string
  year?: number
}

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

TIP: The generic parameter of is deprecated reactive(), because the return value of deep ref unpacking is different from the type of the generic parameter.


Five, for computed()the label type

computed()The type is automatically deduced from the return value of its evaluation 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('')

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

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

6. Mark the type of the event handler function

When handling native DOM events, we should properly annotate the types for the parameters we pass to event handlers. 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 not type-annotated, the eventparameter is implicitly annotated with anytype. This will also throw a TS error when tsconfig.jsonconfigured in "strict": trueor . "noImplicitAny": trueTherefore, it is recommended to explicitly type the parameters of event handler functions. Also, you may need to explicitly cast eventproperties on the :

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

Seven, mark the 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 InjectionKeyinterface, which is a Symbolgeneric type inherited from , which can be used to synchronize the type of the 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 injecting a key with a string, the type of the injected value unknownneeds to be explicitly declared through a generic parameter:

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

8. Annotate the template reference type

Template references need 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(() => {
    
    
  el.value?.focus()
})
</script>

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

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


Nine, refer to the annotation type for the component template

Sometimes, you may need to add a template reference to a child component in order to call methods it exposes. For example, let's say we have a MyModalchild component that has a method that opens 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 MyModalthe type of , we first need to typeofobtain its type through , and then use TypeScript's built-in InstanceTypetool 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/klylove/article/details/126343015
Recomendado
Clasificación