Chào mừng bạn đến với Cafedev, nơi chúng tôi chia sẻ kiến thức và kinh nghiệm về công nghệ! Trong bài viết này, chúng ta sẽ khám phá về Vue.js với TypeScript thông qua Options API. Vue.js, một trong những framework JavaScript phổ biến nhất hiện nay, kết hợp với TypeScript, ngôn ngữ kiểu tĩnh mạnh mẽ, sẽ giúp chúng ta xây dựng ứng dụng web linh hoạt và dễ bảo trì. Hãy cùng nhau tìm hiểu cách sử dụng Options API để viết mã Vue.js có hiệu quả và dễ dàng đọc hiểu!

Trang này giả định bạn đã đọc tổng quan về Sử dụng Vue với TypeScript.

Tips: gợi ý Mặc dù Vue hỗ trợ việc sử dụng TypeScript với Options API, nhưng được khuyến nghị sử dụng Vue với TypeScript thông qua Composition API vì nó cung cấp việc suy luận kiểu đơn giản, hiệu quả và mạnh mẽ hơn.

1. Gán Kiểu Cho Props của Component

Suy luận kiểu cho props trong Options API yêu cầu bọc component bằng defineComponent(). Với điều này, Vue có thể suy luận các loại cho props dựa trên tùy chọn props, lấy các tùy chọn bổ sung như required: truedefault vào tài khoản:

import { defineComponent } from 'vue'

export default defineComponent({
  // type inference enabled
  props: {
    name: String,
    id: [Number, String],
    msg: { type: String, required: true },
    metadata: null
  },
  mounted() {
    this.name // type: string | undefined
    this.id // type: number | string | undefined
    this.msg // type: string
    this.metadata // type: any
  }
})

Tuy nhiên, tùy chọn props trong thời gian chạy chỉ hỗ trợ sử dụng hàm constructor làm kiểu cho prop – không có cách nào để chỉ định các loại phức tạp như các đối tượng với các thuộc tính lồng nhau hoặc đăng ký gọi hàm.

Để chú thích các loại props phức tạp, chúng ta có thể sử dụng loại tiện ích PropType:

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

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

export default defineComponent({
  props: {
    book: {
      // provide more specific type to `Object`
      type: Object as PropType<Book>,
      required: true
    },
    // can also annotate functions
    callback: Function as PropType<(id: number) => void>
  },
  mounted() {
    this.book.title // string
    this.book.year // number

    // TS Error: argument of type 'string' is not
    // assignable to parameter of type 'number'
    this.callback?.('123')
  }
})

Lưu Ý

Nếu phiên bản TypeScript của bạn nhỏ hơn 4.7, bạn phải cẩn thận khi sử dụng giá trị hàm cho các tùy chọn validatordefault của prop – hãy đảm bảo sử dụng hàm mũi tên:

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>,
      // Make sure to use arrow functions if your TypeScript version is less than 4.7
      default: () => ({
        title: 'Arrow Function Expression'
      }),
      validator: (book: Book) => !!book.title
    }
  }
})

Điều này ngăn TypeScript phải suy luận kiểu của this bên trong các hàm này, điều này, không may, có thể gây suy luận kiểu thất bại. Đó là một hạn chế thiết kế trước đây, và bây giờ đã được cải thiện trong TypeScript 4.7.

2. Gán Kiểu Cho Emits cho Component

Chúng ta có thể khai báo kiểu dữ liệu mong đợi cho một sự kiện phát ra sử dụng cú pháp đối tượng của tùy chọn emits. Ngoài ra, tất cả các sự kiện phát ra không được khai báo sẽ ném một lỗi kiểu dữ liệu khi được gọi:

import { defineComponent } from 'vue'

export default defineComponent({
  emits: {
    addBook(payload: { bookName: string }) {
      // perform runtime validation
      return payload.bookName.length > 0
    }
  },
  methods: {
    onSubmit() {
      this.$emit('addBook', {
        bookName: 123 // Type error!
      })

      this.$emit('non-declared-event') // Type error!
    }
  }
})

3. Gán Kiểu Cho Thuộc Tính Computed

Một thuộc tính computed suy luận kiểu của nó dựa trên giá trị trả về của nó:

import { defineComponent } from 'vue'

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

Trong một số trường hợp, bạn có thể muốn chú thích rõ ràng kiểu của một thuộc tính computed để đảm bảo hiện thực của nó là chính xác:

import { defineComponent } from 'vue'

export default defineComponent({
  data() {
    return {
      message: 'Hello!'
    }
  },
  computed: {
    // explicitly annotate return type
    greeting(): string {
      return this.message + '!'
    },

    // annotating a writable computed property
    greetingUppercased: {
      get(): string {
        return this.greeting.toUpperCase()
      },
      set(newValue: string) {
        this.message = newValue.toUpperCase()
      }
    }
  }
})

Chú thích rõ ràng cũng có thể được yêu cầu trong một số trường hợp biên khi TypeScript không thể suy luận kiểu của một thuộc tính computed do vòng lặp suy luận vòng tròn.

4. Gán Kiểu Cho Xử Lý Sự Kiện

Khi làm việc với các sự kiện DOM nguyên thủy, việc gán kiểu cho đối số mà chúng ta truyền vào xử lý viên có thể hữu ích. Hãy xem xét ví dụ sau:

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

export default defineComponent({
  methods: {
    handleChange(event) {
      // `event` implicitly has `any` type
      console.log(event.target.value)
    }
  }
})
</script>
<template>
  <input type="text" @change="handleChange" />
</template>

Nếu không có chú thích kiểu, đối số event sẽ ngầm định có kiểu any. Điều này cũng sẽ dẫn đến lỗi TS nếu "strict": true hoặc "noImplicitAny": true được sử dụng trong tsconfig.json. Do đó, được khuyến nghị chú thích rõ ràng cho đối số của các xử lý sự kiện. Ngoài ra, bạn có thể cần sử dụng khai báo kiểu khi truy cập các thuộc tính của event:

import { defineComponent } from 'vue'

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

5. Mở Rộng Các Thuộc Tính Toàn Cục

Một số plugin cài đặt các thuộc tính có sẵn toàn cục cho tất cả các trường hợp component thông qua app.config.globalProperties. Ví dụ, chúng ta có thể cài đặt this.$http cho việc lấy dữ liệu hoặc this.$translate cho quốc tế hóa. Để làm cho việc này hoạt động tốt với TypeScript, Vue tiếp tục mở rộng một giao diện ComponentCustomProperties được thiết kế để được mở rộng qua Mở rộng module TypeScript:

import axios from 'axios'

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

Xem thêm:
Các bài kiểm thử đơn vị TypeScript cho các loại component mở rộng

5.1 Vị Trí Mở Rộng Kiểu

Chúng ta có thể đặt mở rộng kiểu này trong một tệp .ts, hoặc trong một tệp *.d.ts toàn dự án. Dù cách nào đi nữa, đảm bảo rằng nó được bao gồm trong tsconfig.json. Đối với tác giả thư viện/plugin, tệp này nên được chỉ định trong thuộc tính types trong package.json.

Để tận dụng mở rộng module, bạn cần đảm bảo rằng việc mở rộng được đặt trong một module TypeScript. Nói cách khác, tệp cần chứa ít nhất một import hoặc export ở mức cao nhất, ngay cả khi chỉ là export {}. Nếu việc mở rộng được đặt bên ngoài một module, nó sẽ ghi đè lên các kiểu ban đầu thay vì mở rộng chúng!

// Does not work, overwrites the original types.
declare module 'vue' {
  interface ComponentCustomProperties {
    $translate: (key: string) => string
  }
}
// Works correctly
export {}

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

6. Mở Rộng Các Tùy Chọn Tùy Chỉnh

Một số plugin, ví dụ vue-router, cung cấp hỗ trợ cho các tùy chọn component tùy chỉnh như beforeRouteEnter:

import { defineComponent } from 'vue'

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

Nếu không có việc mở rộng kiểu thích hợp, các đối số của hook này sẽ ngầm định có kiểu any. Chúng ta có thể mở rộng giao diện ComponentCustomOptions để hỗ trợ các tùy chọn tùy chỉnh này:

import { Route } from 'vue-router'

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

Bây giờ tùy chọn beforeRouteEnter sẽ được gán kiểu đúng. Lưu ý rằng đây chỉ là một ví dụ – các thư viện được gán kiểu tốt như vue-router nên tự động thực hiện các mở rộng này trong định nghĩa kiểu của riêng mình.
Vị trí của việc mở rộng này phải tuân thủ các hạn chế tương tự như mở rộng các thuộc tính toàn cục.

Xem thêm:

Các bài kiểm thử đơn vị TypeScript cho các loại component mở rộng

Cafedev hy vọng rằng bài viết này đã giúp bạn hiểu rõ hơn về cách sử dụng Vue.js với TypeScript thông qua Options API. Với sự kết hợp mạnh mẽ này, bạn có thể xây dựng các ứng dụng web linh hoạt và dễ bảo trì. Hãy áp dụng những kiến thức bạn đã học vào dự án của mình và không ngần ngại chia sẻ trên cộng đồng Cafedev để cùng nhau phát triển hơn nữa. Đừng quên đón đọc những nội dung mới và cập nhật từ Cafedev để tiếp tục khám phá thế giới công nghệ!

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của CafeDev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!