Áp dụng Class Component với TypeScript trong Vue.js

Khi làm việc với Vue.js kết hợp TypeScript, việc chuyển đổi sang cú pháp dựa trên class (class-style) giúp mã nguồn rõ ràng và quản lý type hiệu quả hơn. Thư viện vue-property-decorator là lựa chọn phổ biến nhất hiện nay. Về bản chất, thư viện này là sự mở rộng (superset) của vue-class-component (thư viện chính thức của Vue). Nó kế thừa toàn bộ tính năng cơ bản và bổ sung thêm các decorator mạnh mẽ như @Prop, @Emit, @Inject, giúp开发者 không cần引入 quá nhiều nguồn khác nhau. Trong thực tế, bạn chỉ cần cài đặt và sử dụng các thành phần từ vue-property-decorator là đủ.

Để bắt đầu, bạn cần cài đặt gói npm sau:

npm i -S vue-property-decorator

Các decorator và hàm hỗ trợ chính bao gồm: @Component, @Prop, @PropSync, @Model, @Watch, @Emit, @Inject, @Provide, @Ref, @VModel và hàm mixins.

Sử dụng @Component

Decorator @Component được kế thừa trực tiếp từ vue-class-component. Nó nhận một tùy chọn cấu hình (ComponentOptions) để khai báo các thành phần chưa được hỗ trợ bởi decorator khác như components, directives, hoặc filters.

Ví dụ dưới đây minh họa việc khai báo component con và dữ liệu trạng thái.

Template HTML:

<template>
    <div class="layout">
        <h2>{{ mainHeading }}</h2>
        <UserPanel :username="mainHeading"></UserPanel>
        <StatsWidget :username="mainHeading"></StatsWidget>
    </div>
</template>

Viết bằng JavaScript thông thường:

import UserPanel from './UserPanel.vue';
import StatsWidget from './StatsWidget.vue';

export default {
  data() {
    return {
      mainHeading: 'Dữ liệu từ Layout cha'
    };
  },
  components: {
    UserPanel,
    StatsWidget
  }
};

Viết bằng TypeScript với Class Component:

import { Component, Vue } from 'vue-property-decorator';
import UserPanel from './UserPanel.vue';
import StatsWidget from './StatsWidget.vue';

@Component({
  components: {
    UserPanel,
    StatsWidget
  }
})
export default class LayoutContainer extends Vue {
  private mainHeading: string = 'Dữ liệu từ Layout cha';
}

Sử dụng @Emit

Decorator @Emit giúp đơn giản hóa việc gửi sự kiện (emit events) lên component cha.

Cách hoạt động:

  • Nhận một tham số tùy chọn là tên sự kiện (string). Nếu bỏ qua, tên sự kiện sẽ được tự động chuyển đổi từ camelCase sang kebab-case dựa trên tên hàm.
  • Nếu hàm được decorate có giá trị trả về, giá trị đó sẽ được truyền vào $emit làm tham số thứ hai. Nếu không có giá trị trả về, các tham số của hàm sẽ được truyền trực tiếp cho $emit.

Xét ví dụ với một nút bấm kích hoạt sự kiện:

<template>
  <div class="action-area">
    <button @click="handleNotify('Thông báo mới')">Gửi sự kiện</button>
  </div>
</template>

JavaScript:

export default {
  mounted() {
    this.$on('custom-notify', payload => {
      console.log(payload);
    });
  },
  methods: {
    handleNotify(msg) {
      this.$emit('custom-notify', msg);
    }
  }
};

TypeScript:

import { Component, Vue, Emit } from 'vue-property-decorator';

@Component
export default class ActionArea extends Vue {
  mounted() {
    this.$on('custom-notify', (payload: string) => {
      console.log(payload);
    });
  }

  // Tên hàm 'handleNotify' sẽ tự động chuyển thành 'handle-notify'
  @Emit()
  handleNotify(message: string) {
    console.log('Đang thực thi logic trước khi emit...');
  }
}

Bạn cũng có thể tường minh chỉ định tên sự kiện khác với tên hàm:

@Component
export default class CustomForm extends Vue {
  @Emit('form-submitted')
  submitData(payload: object) {
    // Hàm này sẽ emit sự kiện có tên là 'form-submitted'
    // và payload là giá trị trả về
    return payload;
  }
}

Sử dụng @Prop

Decorator @Prop được dùng để khai báo các props nhận từ component cha.

Tham số:

  • Có thể là một Constructor (ví dụ: String, Number) để xác định kiểu.
  • Một mảng Constructor để xác định nhiều kiểu khả thi.
  • Một object PropOptions chứa type, default, required, validator.

Lưu ý quan trọng về TypeScript:
Do props được gán giá trị từ bên ngoài trước khi component khởi tạo, TypeScript sẽ báo lỗi nếu bạn không xử lý trường hợp chưa được gán. Có hai cách giải quyết:

  1. Thêm kiểu | undefined vào khai báo kiểu.
  2. Sử dụng toán tử định nghĩa xác định (definite assignment assertion) ! sau tên thuộc tính (ví dụ: myProp!: string).

Ví dụ chuyển đổi từ Options API sang Class Component:

JavaScript:

export default {
  props: {
    userId: {
      type: Number,
      required: true
    },
    isActive: {
      type: Boolean,
      default: false
    },
    metaData: {
      type: [String, Array]
    }
  }
};

TypeScript với vue-property-decorator:

import { Component, Vue, Prop } from 'vue-property-decorator';

@Component
export default class UserProfile extends Vue {
  // Bắt buộc, xác định bằng Constructor
  @Prop(Number) readonly userId!: number;

  // Có giá trị mặc định, dùng object options
  @Prop({ default: false })
  readonly isActive!: boolean;

  // Nhiều kiểu dữ liệu, dùng mảng Constructor
  @Prop([String, Array])
  readonly metaData!: string | string[];
}

Sử dụng @Ref

Decorator @Ref cung cấp một cách tiện lợi để truy cập vào các phần tử DOM hoặc component con được đánh dấu bằng thuộc tính ref.

Cú pháp: @Ref(refKey?: string)

  • Nếu không cung cấp tham số, tên thuộc tính trong class sẽ được dùng làm key để tìm ref.
  • Nếu cung cấp tham số, tham số đó sẽ được dùng để tìm ref và gán vào thuộc tính.

Ví dụ:

import { Component, Vue, Ref } from 'vue-property-decorator';

@Component
export default class InputForm extends Vue {
  // Tự động map với <input ref="emailInput" />
  @Ref('emailInput') readonly inputElement!: HTMLInputElement;

  focusInput() {
    this.inputElement.focus();
  }
}

Thẻ: Vue.js typescript class-component Decorators vue-property-decorator

Đăng vào ngày 11 tháng 6 lúc 17:24