Przejdź bezpośrednio do treści

TypeScript z Options API

Ta strona zakłada, że przeczytałeś już Używanie Vue z TypeScript.

TIP

Mimo że Vue wspiera użycie TypeScript z Options API, zalecane jest używanie Vue z TypeScript poprzez Composition API, ponieważ oferuje prostsze, wydajniejsze i bardziej niezawodne wnioskowanie typów.

Typowanie propsów komponentu

Wnioskowanie typów dla propsów w Options API wymaga owinięcia komponentu w defineComponent(). Dzięki temu Vue jest w stanie wywnioskować typy dla propsów na podstawie opcji props, biorąc pod uwagę dodatkowe opcje takie jak required: true i default:

ts
import { defineComponent } from 'vue'

export default defineComponent({
  // wnioskowanie typów włączone
  props: {
    name: String,
    id: [Number, String],
    msg: { type: String, required: true },
    metadata: null
  },
  mounted() {
    this.name // typ: string | undefined
    this.id // typ: number | string | undefined
    this.msg // typ: string
    this.metadata // typ: any
  }
})

Jednakże opcje props wykonywane w czasie działania wspierają jedynie użycie funkcji konstruktora jako typ propa - nie ma możliwości określenia złożonych typów, takich jak obiekty z zagnieżdżonymi właściwościami lub sygnaturami wywołania funkcji.

Aby opisać złożone typy propsów, możemy użyć typu pomocniczego PropType:

ts
import { defineComponent } from 'vue'
import type { PropType } from 'vue'

interface Book {
  title: string
  author: string
  year: number
}

export default defineComponent({
  props: {
    book: {
      // podaj bardziej szczegółowy typ dla `Object`
      type: Object as PropType<Book>,
      required: true
    },
    // można również opisać funkcje
    callback: Function as PropType<(id: number) => void>
  },
  mounted() {
    this.book.title // string
    this.book.year // number

    // Błąd TS: argument typu 'string' nie jest
    // przypisywalny do parametru typu 'number'
    this.callback?.('123')
  }
})

Zastrzeżenia

Jeśli używasz wersji TypeScript niższej niż 4.7, musisz zachować ostrożność używając wartości funkcji dla opcji propów validator i default - upewnij się, że używasz funkcji strzałkowych:

ts
import { defineComponent } from 'vue'
import type { PropType } from 'vue'

interface Book {
  title: string
  year?: number
}

export default defineComponent({
  props: {
    bookA: {
      type: Object as PropType<Book>,
      // Upewnij się, że używasz funkcji strzałkowych jeśli Twoja wersja TypeScript jest niższa niż 4.7
      default: () => ({
        title: 'Wyrażenie Funkcji Strzałkowej'
      }),
      validator: (book: Book) => !!book.title
    }
  }
})

Zapobiega to konieczności wnioskowania przez TypeScript typu this wewnątrz tych funkcji, co niestety może powodować niepowodzenie wnioskowania typów. Było to wcześniejsze ograniczenie projektowe, które zostało ulepszone w TypeScript 4.7.

Typowanie emitów komponentu

Możemy zadeklarować oczekiwany typ payload'u dla emitowanego zdarzenia używając składni obiektowej opcji emits. Dodatkowo, wszystkie niezadeklarowane emitowane zdarzenia będą powodować błąd typu podczas wywoływania:

ts
import { defineComponent } from 'vue'

export default defineComponent({
  emits: {
    addBook(payload: { bookName: string }) {
      // wykonaj walidację w czasie wykonania
      return payload.bookName.length > 0
    }
  },
  methods: {
    onSubmit() {
      this.$emit('addBook', {
        bookName: 123 // Błąd typu!
      })

      this.$emit('non-declared-event') // Błąd typu!
    }
  }
})

Typowanie właściwości obliczanych

Właściwość obliczana wywnioskuje swój typ na podstawie zwracanej wartości:

ts
import { defineComponent } from 'vue'

export default defineComponent({
  data() {
    return {
      message: 'Witaj!'
    }
  },
  computed: {
    greeting() {
      return this.message + '!'
    }
  },
  mounted() {
    this.greeting // typ: string
  }
})

W niektórych przypadkach możesz chcieć jawnie opisać typ właściwości obliczanej, aby upewnić się, że jej implementacja jest poprawna:

ts
import { defineComponent } from 'vue'

export default defineComponent({
  data() {
    return {
      message: 'Witaj!'
    }
  },
  computed: {
    // jawnie opisz typ zwracany
    greeting(): string {
      return this.message + '!'
    },

    // opisywanie zapisywalnej właściwości obliczanej
    greetingUppercased: {
      get(): string {
        return this.greeting.toUpperCase()
      },
      set(newValue: string) {
        this.message = newValue.toUpperCase()
      }
    }
  }
})

Jawne adnotacje mogą być również wymagane w niektórych skrajnych przypadkach, gdy TypeScript nie może wywnioskować typu właściwości obliczanej z powodu cyklicznych pętli wnioskowania.

Typowanie Obsługi Zdarzeń

Podczas pracy z natywnymi zdarzeniami DOM, może być przydatne poprawne typowanie argumentu, który przekazujemy do funkcji obsługującej. Przyjrzyjmy się temu przykładowi:

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

export default defineComponent({
  methods: {
    handleChange(event) {
      // `event` niejawnie ma typ `any`
      console.log(event.target.value)
    }
  }
})
</script>

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

Bez adnotacji typu, argument event będzie miał niejawnie typ any. Spowoduje to błąd TS jeśli "strict": true lub "noImplicitAny": true są używane w tsconfig.json. Dlatego zaleca się jawne oznaczenie argumentu handlera zdarzeń. Dodatkowo może być konieczne użycie asercji typu podczas uzyskiwania dostępu do właściwości event:

ts
import { defineComponent } from 'vue'

export default defineComponent({
  methods: {
    handleChange(event: Event) {
      console.log((event.target as HTMLInputElement).value)
    }
  }
})

Rozszerzanie Właściwości Globalnych

Niektóre wtyczki instalują globalnie dostępne właściwości dla wszystkich instancji komponentów poprzez app.config.globalProperties. Na przykład, możemy zainstalować this.$http do pobierania danych lub this.$translate do internacjonalizacji. Aby działało to poprawnie z TypeScript, Vue udostępnia interfejs ComponentCustomProperties zaprojektowany do rozszerzania poprzez rozszerzanie modułów TypeScript:

ts
import axios from 'axios'

declare module 'vue' {
  interface ComponentCustomProperties {
    $http: typeof axios
    $translate: (key: string) => string
  }
}

Zobacz także:

Umiejscowienie Rozszerzenia Typów

Możemy umieścić to rozszerzenie typów w pliku .ts lub w ogólnoprojektowym pliku *.d.ts. W obu przypadkach upewnij się, że jest ono uwzględnione w tsconfig.json. Dla autorów bibliotek/wtyczek, ten plik powinien być określony we właściwości types w package.json.

Aby skorzystać z rozszerzenia modułu, musisz upewnić się, że rozszerzenie jest umieszczone w module TypeScript. Oznacza to, że plik musi zawierać co najmniej jeden import lub export na najwyższym poziomie, nawet jeśli jest to tylko export {}. Jeśli rozszerzenie zostanie umieszczone poza modułem, nadpisze oryginalne typy zamiast je rozszerzać!

ts
// Nie działa, nadpisuje oryginalne typy.
declare module 'vue' {
  interface ComponentCustomProperties {
    $translate: (key: string) => string
  }
}
ts
// Działa poprawnie
export {}

declare module 'vue' {
  interface ComponentCustomProperties {
    $translate: (key: string) => string
  }
}

Rozszerzanie Opcji Niestandardowych

Niektóre wtyczki, na przykład vue-router, zapewniają wsparcie dla niestandardowych opcji komponentów, takich jak beforeRouteEnter:

ts
import { defineComponent } from 'vue'

export default defineComponent({
  beforeRouteEnter(to, from, next) {
    // ...
  }
})

Bez odpowiedniego rozszerzenia typów, argumenty tego hooka będą miały niejawnie typ any. Możemy rozszerzyć interfejs ComponentCustomOptions, aby obsługiwał te niestandardowe opcje:

ts
import { Route } from 'vue-router'

declare module 'vue' {
  interface ComponentCustomOptions {
    beforeRouteEnter?(to: Route, from: Route, next: () => void): void
  }
}

Teraz opcja beforeRouteEnter będzie miała właściwe typy. Zauważ, że to tylko przykład - dobrze typowane biblioteki jak vue-router powinny automatycznie wykonywać te rozszerzenia w swoich własnych definicjach typów.

Umiejscowienie tego rozszerzenia podlega tym samym ograniczeniom co rozszerzenia właściwości globalnych.

Zobacz także:

TypeScript z Options APIJest załadowany