Hướng dẫn toàn diện Vue 3 với TypeScript và Composition API

Vue 3 mang đến kiến trúc mới dựa trên Composition API, loại bỏ các tùy chọn như data, methods, computed hay watch ở cấp độ tùy chọn — thay vào đó, mọi logic đều được tổ chức trong hàm setup(). Đây là nền tảng để xây dựng ứng dụng linh hoạt, dễ kiểm thử và hỗ trợ mạnh mẽ cho TypeScript.

Tự động biên dịch TypeScript

Để kích hoạt giám sát và biên dịch tự động mã TypeScript trong dự án Vue 3:

tsc --watch --noEmit false

Hoặc cấu hình tsconfig.json với "composite": true và tích hợp vào script dev trong package.json.

Export trong module ES

Thay vì export default kiểu Options API, Vue 3 khuyến khích sử dụng export rõ ràng cho các hàm, hằng số hoặc composable:

export const useCounter = () => {
  const count = ref(0);
  const increment = () => count.value++;
  return { count, increment };
};

Binding dữ liệu và thuộc tính

Sử dụng cú pháp {{ }}, v-bind (hoặc ký hiệu rút gọn :) và v-html:

<div :class="['box', isActive ? 'active' : '']">
  <p v-html="rawHtml"></p>
  <img :src="avatarUrl" :alt="user.name" />
</div>

Binding sự kiện và xử lý tương tác

Các trình xử lý sự kiện được gắn qua @click, @keyup.enter, v.v. Trong Composition API, chúng thường được định nghĩa trực tiếp trong setup():

const handleSubmit = () => {
  if (formValid.value) {
    api.submit(formData.value).then(() => {
      notification.success('Gửi thành công');
    });
  }
};

Truy cập DOM với ref

Không dùng this.$refs như Vue 2, mà khai báo ref và gắn vào phần tử:

const inputRef = ref(null);

onMounted(() => {
  inputRef.value?.focus();
});
<input ref="inputRef" type="text" />

Two-way binding với v-model

Với component tùy chỉnh, v-model được triển khai bằng cách phát sự kiện update:propName:

// Con
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);

const updateValue = (val) => emit('update:modelValue', val);
<input 
  :value="props.modelValue" 
  @input="updateValue($event.target.value)" 
/>

Props và truyền dữ liệu giữa component

Cha → con thông qua props, con → cha qua emit. Với nhiều giá trị, có thể dùng defineModel (Vue 3.4+):

const model = defineModel('checked');

Truyền dữ liệu không theo props (non-prop attributes) sẽ tự động lan xuống phần tử gốc — tắt bằng inheritAttrs: false và phân phối thủ công bằng v-bind="$attrs".

Slot và nội dung động

Slot mặc định, có tên và slot có phạm vi (scoped slot) được hỗ trợ đầy đủ:

<UserProfile>
  <template #header>
    <h2>Thông tin cá nhân</h2>
  </template>
  <template #default="{ user }">
    <p>{{ user.email }}</p>
  </template>
</UserProfile>

Composition API cốt lõi

  • ref(): tạo reactive primitive
  • reactive(): tạo reactive object
  • toRefs(): chuyển reactive object thành tập ref độc lập
  • computed(): tính toán dựa trên dependency
  • watch()watchEffect(): quan sát thay đổi, khác biệt ở cơ chế tracking tự động
  • provide()/inject(): chia sẻ trạng thái giữa các tầng component

Chu kỳ sống và tối ưu hóa

Các hook như onMounted, onUnmounted, onBeforeUpdate thay thế mounted, beforeDestroy. Để đảm bảo DOM đã cập nhật sau thay đổi phản ứng:

await nextTick();
// DOM đã render xong

Giữ trạng thái component với <KeepAlive>

Giúp cache trạng thái component khi chuyển đổi (ví dụ: tab, route):

<KeepAlive include="UserProfile">
  <RouterView />
</KeepAlive>

Routing với Vue Router 4

Cấu hình tuyến đường dạng composition-based:

const routes = [
  { path: '/user/:id', component: UserPage, props: true },
  { path: '/admin', name: 'admin', component: AdminPanel }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

Dùng useRouter()useRoute() trong setup() để điều hướng và đọc tham số.

Quản lý trạng thái với Pinia (thay Vuex)

Pinia là giải pháp chính thức cho quản lý trạng thái trong Vue 3:

export const useAuthStore = defineStore('auth', {
  state: () => ({ token: '', user: null }),
  actions: {
    async login(credentials) {
      const res = await api.login(credentials);
      this.token = res.token;
      this.user = res.user;
    }
  }
});

Tích hợp TypeScript

Khai báo kiểu tường minh cho props, emits và store:

interface User {
  id: number;
  name: string;
  email: string;
}

const props = defineProps<{
  user: User;
  editable?: boolean;
}>();

Yêu cầu HTTP và xử lý JSONP

Dùng axios cho REST API; với endpoint yêu cầu JSONP (ví dụ: API thời tiết cũ), kết hợp fetch-jsonp:

import jsonp from 'fetch-jsonp';

const fetchWeather = async (city) => {
  const res = await jsonp(`https://api.example.com/weather?city=${city}`);
  return res.json();
};

Teleport và quản lý vị trí DOM

Render nội dung ra ngoài cây DOM hiện tại — hữu ích cho modal, tooltip:

<Teleport to="#modal-root">
  <Modal v-if="showModal" @close="showModal = false" />
</Teleport>

Mixins và tái sử dụng logic

Thay vì mixins (đã lỗi thời), nên dùng composable functions — ví dụ useDebounce:

export function useDebounce(fn, delay) {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

Serverless và tích hợp backend

Vue frontend có thể kết nối với các hàm serverless (Vercel Functions, AWS Lambda) thông qua API routes — giảm phụ thuộc vào server truyền thống và tăng khả năng mở rộng.

Thẻ: Vue3 typescript composition-api pinia vue-router

Đăng vào ngày 15 tháng 9 lúc 10:57