阿里云主机折上折
  • 微信号
Current Site:Index > TypeScript supports augmentation

TypeScript supports augmentation

Author:Chuan Chen 阅读数:26549人阅读 分类: Vue.js

TypeScript support in the Vue.js ecosystem has significantly improved in recent years, with enhanced type support across the core library and surrounding toolchain. Developers can now enjoy stricter type checking, smarter code hints, and a smoother development experience.

Deep Integration of TypeScript with Vue 3

Vue 3 was designed from the ground up with native TypeScript support in mind. Unlike Vue 2, which required decorators or class-component for type support, Vue 3's Composition API naturally lends itself to type inference. For example:

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

// Automatically inferred as Ref<number>
const count = ref(0)

// Explicitly specifying the type
const user = ref<{ name: string; age: number }>({
  name: 'Alice',
  age: 25
})

function increment() {
  count.value++ // Fully type-safe
}
</script>

This integration offers several key advantages:

  • Automatic type checking for template expressions
  • Runtime type validation for component props
  • Better IDE autocompletion support

Type Definitions for Component Props

Vue 3 provides multiple ways to define component prop types:

import { defineComponent } from 'vue'

// Method 1: Runtime declaration
export default defineComponent({
  props: {
    title: String,
    count: {
      type: Number,
      required: true
    }
  }
})

// Method 2: Pure TypeScript types
export default defineComponent({
  props: {
    title: {
      type: String as PropType<'primary' | 'secondary'>,
      default: 'primary'
    },
    items: {
      type: Array as PropType<{ id: number; text: string }[]>,
      required: true
    }
  }
})

// Method 3: Using generics
interface User {
  name: string
  age: number
}

export default defineComponent({
  props: {
    user: {
      type: Object as PropType<User>,
      required: true
    }
  }
})

Type Advantages of the Composition API

The Composition API is particularly well-suited for TypeScript due to its functional style, which better preserves type information:

import { ref, computed } from 'vue'

interface Todo {
  id: number
  text: string
  completed: boolean
}

export function useTodo() {
  const todos = ref<Todo[]>([])
  
  const completedCount = computed(() => {
    return todos.value.filter(todo => todo.completed).length
  })

  function addTodo(text: string) {
    todos.value.push({
      id: Date.now(),
      text,
      completed: false
    })
  }

  return {
    todos,
    completedCount,
    addTodo
  }
}

Template Refs and Component Instance Types

When using refs in templates, you can precisely specify the ref type:

<template>
  <ChildComponent ref="childRef" />
  <input ref="inputRef" />
</template>

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

const childRef = ref<InstanceType<typeof ChildComponent>>()
const inputRef = ref<HTMLInputElement>()

onMounted(() => {
  // Fully type-safe method call
  childRef.value?.someMethod()
  
  // Type-safe DOM element access
  inputRef.value?.focus()
})
</script>

Global Type Extensions

You can extend Vue's global type declarations to support custom options:

// types/vue.d.ts
import { ComponentCustomProperties } from 'vue'

declare module 'vue' {
  interface ComponentCustomProperties {
    $filters: {
      formatDate: (date: Date) => string
    }
  }
}

// Usage
const app = createApp({})
app.config.globalProperties.$filters = {
  formatDate: (date: Date) => date.toLocaleDateString()
}

// Usage in components
const formatted = vm.$filters.formatDate(new Date()) // Type-safe

Integration with Vue Router

Vue Router 4 provides full TypeScript support:

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/user/:id',
      component: () => import('./views/User.vue'),
      props: route => ({
        id: Number(route.params.id), // Automatic type conversion
        query: route.query.search
      })
    }
  ]
})

// Usage in components
import { useRoute } from 'vue-router'

const route = useRoute()
// route.params.id is correctly inferred as string | string[]

Type Support in Pinia State Management

Pinia, Vue's official state management library, offers excellent TypeScript support:

import { defineStore } from 'pinia'

interface UserState {
  name: string
  age: number
  preferences: {
    theme: 'light' | 'dark'
    notifications: boolean
  }
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    name: '',
    age: 0,
    preferences: {
      theme: 'light',
      notifications: true
    }
  }),
  actions: {
    updateName(newName: string) {
      this.name = newName // Fully type-safe
    },
    toggleTheme() {
      this.preferences.theme = 
        this.preferences.theme === 'light' ? 'dark' : 'light'
    }
  },
  getters: {
    isAdult: (state) => state.age >= 18
  }
})

// Usage in components
const store = useUserStore()
store.updateName('Alice') // Type-checked

Toolchain Improvements

Modern Vue toolchains have also improved TypeScript support:

  1. Vite includes fast TypeScript compilation out of the box.
  2. Vitest provides a seamless type testing experience.
  3. Volar has replaced Vetur as the officially recommended IDE extension.
// Vitest test example
import { mount } from '@vue/test-utils'
import MyComponent from './MyComponent.vue'

describe('MyComponent', () => {
  it('renders correctly', () => {
    const wrapper = mount(MyComponent, {
      props: {
        // Autocompletion and type checking
        title: 'Test',
        count: 1
      }
    })
    
    expect(wrapper.text()).toContain('Test')
  })
})

Common Issues and Solutions

1. Missing Types in Third-Party Libraries

For libraries without type definitions, create declaration files:

// shims.d.ts
declare module 'untyped-library' {
  export function doSomething(options: {
    foo: string
    bar?: number
  }): Promise<void>
}

2. Complex Expressions in Templates

Use computed properties to maintain type safety with complex template expressions:

<template>
  <div>{{ formattedDate }}</div>
</template>

<script setup lang="ts">
import { computed } from 'vue'

const props = defineProps<{
  date: Date
  format?: 'short' | 'long'
}>()

const formattedDate = computed(() => {
  return props.format === 'short' 
    ? props.date.toLocaleDateString() 
    : props.date.toLocaleString()
})
</script>

3. Handling Dynamic Components

Use markRaw and type assertions for dynamic components:

import { markRaw } from 'vue'
import type { Component } from 'vue'

const components: Record<string, Component> = {
  home: markRaw(defineAsyncComponent(() => import('./Home.vue'))),
  about: markRaw(defineAsyncComponent(() => import('./About.vue')))
}

const currentComponent = ref<Component>(components.home)

Performance Optimization with Type Safety

TypeScript not only ensures type safety but also aids in performance optimization:

// Use const enums to reduce runtime code
const enum Routes {
  HOME = '/',
  ABOUT = '/about'
}

// Use literal types to restrict options
type Theme = 'light' | 'dark' | 'system'

interface Settings {
  theme: Theme
  animations: boolean
}

const settings = reactive<Settings>({
  theme: 'light',
  animations: true
})

Combining with JSX/TSX

Vue 3 supports writing components with TSX for better type support:

import { defineComponent } from 'vue'

interface ButtonProps {
  type?: 'primary' | 'danger'
  onClick?: (event: MouseEvent) => void
}

const Button = defineComponent({
  setup(props: ButtonProps, { slots }) {
    return () => (
      <button 
        class={`btn-${props.type || 'default'}`}
        onClick={props.onClick}
      >
        {slots.default?.()}
      </button>
    )
  }
})

// Usage
<Button type="primary" onClick={(e) => console.log(e)}>
  Click me
</Button>

Type-Safe Dependency Injection

Vue's provide/inject API also supports type safety:

import { inject, provide } from 'vue'

const ThemeSymbol = Symbol() as InjectionKey<'light' | 'dark'>

// Ancestor component
provide(ThemeSymbol, 'dark')

// Descendant component
const theme = inject(ThemeSymbol, 'light') // Default value 'light'

Advanced Type Patterns

Leverage TypeScript's advanced features to enhance Vue development:

// Conditional types
type ExtractComponentProps<T> = T extends new () => { $props: infer P } 
  ? P 
  : never

// Mapped types
type OptionalProps<T> = {
  [K in keyof T]?: T[K]
}

// Utility types
interface BaseProps {
  id: string
  class?: string
}

type WithDefaults<T, D> = Omit<T, keyof D> & {
  [K in keyof D]: K extends keyof T ? T[K] : never
}

function defineDefaults<T, D>(props: T, defaults: D): WithDefaults<T, D> {
  return { ...defaults, ...props } as any
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.