26 mẹo phát triển Vue 3 hiệu quả
Vue đã thay đổi đáng kể so với giai đoạn Vue 2 phổ biến vào năm 2019. Nhiều mẹo từng hữu ích vẫn còn giá trị, nhưng một số đã trở nên lỗi thời hoặc cần được diễn giải lại trong bối cảnh Vue 3, Composition API, Vite, Pinia, TypeScript và các công cụ hiện đại.
Bài viết này tổng hợp 26 mẹo phát triển Vue theo hướng thực tế cho năm 2026: giảm mã thừa, tránh các lỗi kiến trúc phổ biến, cải thiện khả năng bảo trì và sử dụng đúng công cụ thay vì chỉ dựa vào thói quen cũ.
1. Sử dụng Pinia thay vì bắt đầu dự án mới với Vuex
Trong các dự án Vue 3 mới, Pinia là lựa chọn phù hợp hơn Vuex.
Vuex vẫn có thể xuất hiện trong các dự án cũ, nhưng nếu bắt đầu một dự án mới, bạn nên ưu tiên:
- Composables cho logic dùng chung đơn giản.
- Pinia cho state dùng chung giữa nhiều màn hình hoặc nhiều tính năng.
- Local state cho dữ liệu chỉ thuộc về một component.
Không phải mọi state đều cần đưa vào store.
Ví dụ, trạng thái mở hoặc đóng một modal chỉ được sử dụng trong một component thì không cần Pinia. Ngược lại, thông tin người dùng đăng nhập, giỏ hàng, quyền truy cập hoặc cấu hình ứng dụng thường phù hợp với store.
// stores/auth.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const user = ref<User | null>(null)
function setUser(value: User | null) {
user.value = value
}
return {
user,
setUser,
}
})Nguyên tắc đơn giản:
Chỉ đưa state vào store khi nó thực sự cần được chia sẻ hoặc quản lý ở cấp ứng dụng.
2. Hiểu rõ component instance và Composition API
Trong Vue 2, nhiều logic thường xoay quanh this:
export default {
data() {
return {
count: 0,
}
},
methods: {
increment() {
this.count++
},
},
}Trong Vue 3, Composition API giúp tổ chức logic theo tính năng thay vì chia nhỏ theo data, methods, computed và watch.
<script setup lang="ts">
import { computed, ref } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} / {{ doubled }}
</button>
</template>Composition API giúp:
- Giảm phụ thuộc vào
this. - Dễ tái sử dụng logic.
- Dễ kiểm thử.
- Dễ tách logic phức tạp thành composable.
- Phù hợp hơn với TypeScript.
Tuy nhiên, Options API không phải là sai. Nó vẫn phù hợp với component đơn giản hoặc codebase đang sử dụng Options API nhất quán.
Vấn đề không phải API nào tốt tuyệt đối, mà là tránh trộn lẫn nhiều phong cách một cách thiếu chủ đích.
3. Đừng ép component render lại nếu chưa hiểu nguyên nhân
Một số lập trình viên sử dụng các cách như:
key.value++hoặc:
await nextTick()chỉ để ép giao diện cập nhật.
Trong Vue, việc render lại thường được xử lý tự động thông qua hệ thống reactivity. Nếu bạn phải liên tục ép component render lại, rất có thể:
- Dữ liệu không reactive.
- Bạn đang thay đổi sai kiểu dữ liệu.
- Bạn đang destructure reactive state không đúng cách.
- Bạn đang giữ một giá trị đã bị mất liên kết reactive.
- Component đang có thiết kế state không rõ ràng.
- Bạn đang dùng DOM trực tiếp thay vì điều khiển dữ liệu.
Hãy tìm nguyên nhân trước khi dùng thủ thuật force update.
Việc dùng key để reset component đôi khi vẫn hợp lý, nhưng nên là quyết định có chủ đích, không phải cách chữa cháy mặc định.
4. Tận dụng Fragment và nhiều phần tử root
Vue 2 yêu cầu mỗi component phải có một phần tử root duy nhất:
<template>
<div>
<header />
<main />
</div>
</template>Vue 3 hỗ trợ nhiều phần tử root:
<template>
<header />
<main />
<footer />
</template>Điều này giúp giảm các wrapper không cần thiết trong DOM.
Tuy nhiên, không nên loại bỏ mọi wrapper một cách máy móc. Một wrapper vẫn có thể cần thiết cho:
- Layout.
- Flexbox hoặc Grid.
- Accessibility.
- Quản lý spacing.
- Grouping logic.
- Transition.
- CSS selector.
Mục tiêu không phải là có ít thẻ HTML nhất, mà là có cấu trúc DOM rõ ràng và có ý nghĩa.
5. Đừng tự xây hệ thống validation quá sớm
Validation form thường bắt đầu bằng vài điều kiện đơn giản:
if (!email.value) {
errors.email = 'Email is required'
}Nhưng khi form lớn dần, bạn có thể phải xử lý:
- Required.
- Email.
- Min/max length.
- Confirm password.
- Async validation.
- Validation theo điều kiện.
- Hiển thị lỗi theo từng field.
- Reset lỗi.
- Validate khi blur hoặc submit.
- Schema dùng chung giữa frontend và backend.
Lúc này, việc tự viết toàn bộ hệ thống validation có thể tạo ra nhiều mã lặp.
Bạn có thể cân nhắc:
- VeeValidate.
- Zod.
- Valibot.
- Vuelidate nếu dự án đã sử dụng nó.
- Schema validation dùng chung với backend khi phù hợp.
Không nhất thiết phải dùng thư viện cho mọi form. Với form nhỏ, validation thủ công vẫn dễ đọc hơn. Nhưng với form phức tạp, schema-based validation thường giúp giảm đáng kể độ phức tạp.
6. Thiết kế component bằng contract rõ ràng
Một component tốt không chỉ là component có ít dòng code. Nó cần có contract rõ ràng:
- Nhận props gì?
- Emit event gì?
- Có slot nào?
- Giá trị mặc định là gì?
- Trạng thái loading được xử lý thế nào?
- Có hỗ trợ keyboard không?
- Có thể tái sử dụng trong ngữ cảnh nào?
- Component có phụ thuộc trực tiếp vào store hoặc route không?
Ví dụ:
<script setup lang="ts">
defineProps<{
title: string
disabled?: boolean
}>()
const emit = defineEmits<{
submit: []
}>()
</script>Một component nên có API bên ngoài đơn giản, dễ đoán và ổn định.
Tránh những component:
- Nhận quá nhiều props.
- Có quá nhiều event.
- Tự gọi API không rõ ràng.
- Tự đọc route, store và localStorage cùng lúc.
- Có quá nhiều trách nhiệm.
- Không thể sử dụng nếu thiếu một loạt context ngầm.
Nếu một component cần quá nhiều props, có thể vấn đề nằm ở việc component đang làm quá nhiều việc.
7. Tạo transition có thể tái sử dụng
Vue cung cấp <Transition> và <TransitionGroup> để xử lý animation theo trạng thái.
<Transition name="fade">
<div v-if="visible">
Nội dung
</div>
</Transition>.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}Bạn nên chuẩn hóa các transition phổ biến:
- Fade.
- Slide.
- Scale.
- Collapse.
- Dialog.
- Dropdown.
- Page transition.
Không nên viết lại cùng một nhóm CSS animation trong hàng chục component.
Tuy nhiên, animation cần phục vụ trải nghiệm người dùng. Không nên thêm transition vào mọi thứ chỉ vì có thể làm được.
Hãy cân nhắc:
- Tốc độ.
- Khả năng nhận biết trạng thái.
- Reduced motion.
- Hiệu suất trên thiết bị yếu.
- Việc animation có làm chậm thao tác hay không.
8. Chọn HTTP client theo nhu cầu thực tế
Axios vẫn là một thư viện tốt, nhưng không phải mọi dự án đều bắt buộc phải dùng Axios.
Trong trình duyệt hiện đại, fetch đã đủ cho nhiều trường hợp:
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('Failed to fetch users')
}
const users = await response.json()Bạn nên dùng fetch khi:
- API đơn giản.
- Không cần interceptor phức tạp.
- Không cần nhiều tính năng đặc thù.
- Muốn giảm dependency.
Axios có thể phù hợp khi cần:
- Interceptor.
- Tự động xử lý request/response.
- Timeout thuận tiện.
- Cấu hình client dùng chung.
- Hệ thống API lớn.
- Tương thích với kiến trúc hiện tại.
Điều quan trọng là không tạo HTTP request trực tiếp rải rác trong mọi component.
Hãy tách logic gọi API thành composable hoặc service khi ứng dụng bắt đầu phức tạp.
9. Sử dụng Vue Router đúng vai trò
Vue Router là router chính thức dành cho Vue.
Router nên chịu trách nhiệm cho:
- URL.
- Điều hướng.
- Route params.
- Query params.
- Nested routes.
- Navigation guards.
- Lazy loading route.
- Metadata của route.
Ví dụ lazy loading:
const routes = [
{
path: '/settings',
component: () => import('@/views/SettingsView.vue'),
},
]Không nên dùng Pinia để thay thế hoàn toàn router.
Nếu một bộ lọc có thể được chia sẻ qua URL, hãy cân nhắc lưu nó trong query:
/products?category=books&sort=priceĐiều này giúp:
- Reload trang vẫn giữ trạng thái.
- Chia sẻ URL.
- Browser back/forward hoạt động đúng.
- Deep linking dễ dàng.
- SEO hoặc SSR thuận tiện hơn khi cần.
10. Không còn sử dụng Vue 2 filters
Trong Vue 2, filters thường được viết như sau:
{{ message | capitalize }}Vue 3 đã loại bỏ filters.
Thay vào đó, hãy dùng:
- Computed.
- Function.
- Composable.
- Utility function.
function capitalize(value: string) {
return value.charAt(0).toUpperCase() + value.slice(1)
}Trong template:
{{ capitalize(message) }}Nếu giá trị được sử dụng nhiều lần:
const formattedMessage = computed(() => capitalize(message.value))Không nên nhồi quá nhiều logic phức tạp trực tiếp vào template. Template nên mô tả giao diện, không nên trở thành nơi xử lý nghiệp vụ.
11. Đọc warning và error thay vì bỏ qua
Vue cung cấp warning khá hữu ích, đặc biệt trong môi trường development.
Một số lỗi thường gặp:
- Mutate prop trực tiếp.
- Key không ổn định.
- Component không được resolve.
- Hydration mismatch.
- Reactive dependency không đúng.
- Event handler không tồn tại.
- Template có lỗi.
- Dùng API không tương thích.
Đừng chỉ tắt warning để console “sạch”.
Một warning bị bỏ qua hôm nay có thể trở thành bug khó truy tìm trong production.
Bạn nên:
- Đọc toàn bộ thông báo.
- Tìm nguyên nhân gốc.
- Kiểm tra stack trace.
- Tạo reproduction nhỏ nếu cần.
- Sửa kiến trúc thay vì thêm workaround.
Vue Devtools cũng rất hữu ích để kiểm tra component tree, state, events và hiệu năng.
12. Chỉ dùng JSX khi nó thực sự phù hợp
Vue hỗ trợ JSX/TSX, nhưng không có nghĩa là mọi component nên viết bằng JSX.
JSX có thể hữu ích khi:
- Render logic rất động.
- Tạo component framework.
- Viết render function.
- Xây dựng thư viện UI.
- Cần kiểm soát cấu trúc render bằng JavaScript.
- Component có nhiều nhánh render phức tạp.
Tuy nhiên, Single File Component với template thường dễ đọc hơn đối với giao diện thông thường.
Hãy chọn công cụ phù hợp với loại component thay vì biến mọi giao diện thành JSX.
13. Dùng CSS cho hover thay vì tạo reactive state không cần thiết
Một lỗi phổ biến là dùng JavaScript để theo dõi hover:
const isHovered = ref(false)<div
@mouseenter="isHovered = true"
@mouseleave="isHovered = false"
>
...
</div>Nếu mục đích chỉ là thay đổi giao diện khi hover, CSS đã đủ:
.card:hover {
transform: translateY(-2px);
}Chỉ dùng reactive state khi hover thực sự ảnh hưởng đến logic:
- Hiển thị dữ liệu bổ sung.
- Gọi API.
- Kích hoạt tooltip phức tạp.
- Thay đổi hành vi.
- Theo dõi trạng thái tương tác vì mục đích accessibility hoặc analytics.
Nguyên tắc:
Đừng dùng JavaScript để giải quyết vấn đề mà CSS đã giải quyết tốt.
14. Sử dụng defineModel() cho component có v-model
Trong Vue 3.4+, defineModel() giúp đơn giản hóa việc xây dựng component hỗ trợ v-model.
Cách truyền thống yêu cầu khai báo prop và emit:
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()Với defineModel():
<script setup lang="ts">
const model = defineModel<string>()
</script>
<template>
<input v-model="model" />
</template>Component cha:
<MyInput v-model="username" />Cách này làm giảm boilerplate và giúp component form dễ đọc hơn.
Bạn vẫn cần hiểu:
v-modellà sự kết hợp giữa prop và event.- Component con không nên tự ý thay đổi dữ liệu ngoài contract.
- Cần xử lý kiểu dữ liệu và giá trị mặc định rõ ràng.
- Không nên lạm dụng nhiều model nếu API component trở nên khó hiểu.
15. Hiểu đúng về this trong JavaScript
Trong Composition API với <script setup>, bạn thường không cần dùng this.
Tuy nhiên, vấn đề về this vẫn tồn tại trong JavaScript, đặc biệt khi làm việc với:
- Callback.
- Event handler.
- Method được truyền làm reference.
- Class.
- Options API.
- Thư viện bên ngoài.
Ví dụ:
const user = {
name: 'Tuan',
greet() {
console.log(this.name)
},
}
const greet = user.greet
greet()Ở đây, this không còn trỏ tới user.
Nếu cần giữ context:
const greet = user.greet.bind(user)Trong Vue 3, việc ưu tiên Composition API giúp giảm nhiều lỗi liên quan đến this, nhưng không thay thế việc hiểu JavaScript nền tảng.
16. Đừng phụ thuộc quá mức vào CSS framework
Tailwind CSS, Bootstrap, UnoCSS hoặc các hệ thống UI khác có thể giúp tăng tốc phát triển. Nhưng việc sử dụng framework không thay thế cho việc thiết kế hệ thống giao diện.
Một dự án tốt nên có:
- Design tokens.
- Màu sắc nhất quán.
- Typography scale.
- Spacing scale.
- Border radius.
- Shadow.
- Component states.
- Responsive rules.
- Dark mode nếu cần.
- Accessibility guidelines.
Nếu mỗi component tự chọn một màu, một khoảng cách và một kiểu button khác nhau, dự án sẽ nhanh chóng trở nên thiếu nhất quán.
CSS framework là công cụ. Design system mới là nền tảng.
17. Tránh nested watchers quá sâu
Watchers hữu ích khi cần phản ứng với thay đổi và thực hiện side effect:
watch(searchQuery, () => {
fetchResults()
})Nhưng nhiều watcher lồng nhau thường là dấu hiệu thiết kế state chưa tốt.
Các vấn đề có thể xảy ra:
- Khó theo dõi luồng dữ liệu.
- Dễ tạo vòng lặp.
- Khó cleanup.
- Khó kiểm thử.
- Side effect xảy ra ngoài dự đoán.
- Hiệu suất kém.
Hãy ưu tiên:
computedcho giá trị dẫn xuất.- Một watcher duy nhất cho một side effect.
- Composable để gom logic.
- Explicit function khi cần điều phối nhiều bước.
watchEffectkhi dependency tự nhiên và đơn giản.
Nếu một giá trị chỉ phụ thuộc vào các state khác, hãy dùng computed thay vì watcher.
18. Luôn xử lý loading, error và empty state
Một giao diện gọi API không chỉ có hai trạng thái “có dữ liệu” và “không có dữ liệu”.
Thông thường cần ít nhất:
- Initial state.
- Loading state.
- Success state.
- Empty state.
- Error state.
- Retry state.
- Partial state nếu API trả về một phần dữ liệu.
<template>
<div v-if="loading">
Loading...
</div>
<div v-else-if="error">
Không thể tải dữ liệu.
<button @click="reload">Thử lại</button>
</div>
<div v-else-if="items.length === 0">
Chưa có dữ liệu.
</div>
<ItemList v-else :items="items" />
</template>Việc mô hình hóa trạng thái rõ ràng giúp giảm bug và làm cho component dễ kiểm thử hơn.
19. Thiết kế props nhỏ, rõ ràng và có kiểu dữ liệu
Props là contract từ component cha xuống component con.
Ví dụ tốt:
defineProps<{
title: string
description?: string
disabled?: boolean
}>()Một object config khổng lồ thường khiến component khó sử dụng và khó biết thuộc tính nào thực sự cần thiết.
Thay vì:
<UserCard :config="userCardConfig" />Có thể rõ ràng hơn:
<UserCard
:name="user.name"
:avatar="user.avatar"
:role="user.role"
:disabled="user.isDisabled"
/>Tuy nhiên, không nên cực đoan biến mọi thứ thành hàng chục props. Nếu một nhóm dữ liệu thực sự có ý nghĩa như một object domain, việc truyền object vẫn hợp lý.
Mục tiêu là contract rõ ràng, không phải số lượng props thấp nhất.
20. Biết khi nào dùng computed và khi nào dùng watch
Dùng computed khi
Bạn cần tính toán một giá trị từ state khác:
const total = computed(() =>
cart.value.reduce((sum, item) => sum + item.price, 0),
)Dùng watch khi
Bạn cần thực hiện side effect khi dữ liệu thay đổi:
watch(route, () => {
document.title = route.meta.title
})Side effect có thể bao gồm:
- Gọi API.
- Ghi localStorage.
- Cập nhật document title.
- Gửi analytics.
- Đồng bộ với thư viện bên ngoài.
- Thực hiện thao tác bất đồng bộ.
Không nên dùng watcher để tạo ra dữ liệu mà computed có thể tính toán.
Sai:
const total = ref(0)
watch(items, () => {
total.value = items.value.reduce(...)
})Tốt hơn:
const total = computed(() =>
items.value.reduce(...),
)Quy tắc:
computedtạo ra giá trị;watchthực hiện tác động phụ.
21. Hiểu các thay đổi và bẫy phổ biến của Vue 3
Một số thói quen từ Vue 2 không còn đúng trong Vue 3:
- Filters đã bị loại bỏ.
- Vuex không còn là lựa chọn mặc định cho dự án mới.
beforeDestroyvàdestroyedđược thay bằngbeforeUnmountvàunmounted.- Một component có thể có nhiều root node.
- Composition API thay đổi cách tổ chức logic.
refcần sử dụng.valuetrong JavaScript.- Một số API global đã thay đổi.
- Plugin phải được đăng ký trên app instance.
- Một số behavior liên quan đến attribute fallthrough thay đổi.
- Cơ chế reactivity dựa trên Proxy khác với Vue 2.
Khi nâng cấp từ Vue 2 lên Vue 3, không nên chỉ sửa lỗi compile. Bạn cần kiểm tra cả:
- State management.
- Router.
- Form handling.
- Lifecycle.
- Plugin.
- Test.
- Build tool.
- SSR hoặc hydration.
- Third-party component libraries.
22. Hiểu rõ khác biệt giữa ref, reactive và destructuring
Đây là nguồn gây lỗi phổ biến trong Composition API.
ref
const count = ref(0)
count.value++reactive
const state = reactive({
count: 0,
})
state.count++Một lỗi thường gặp:
const state = reactive({
count: 0,
})
const { count } = stateViệc destructure như trên có thể làm mất liên kết reactive trong nhiều trường hợp.
Nếu cần destructure reactive object mà vẫn giữ reactivity:
const { count } = toRefs(state)Hoặc sử dụng storeToRefs() khi làm việc với Pinia store.
Đừng coi ref và reactive là hai cách viết hoàn toàn tương đương. Hãy chọn dựa trên cách bạn muốn truyền, destructure và tái sử dụng dữ liệu.
23. Hiểu đúng lifecycle và vấn đề SSR
onMounted() chỉ chạy sau khi component được mount trên client.
Đây là nơi phù hợp để truy cập:
window.document.- DOM.
- Browser API.
- Local storage.
- Một số thư viện chỉ chạy trên client.
Nhưng nếu ứng dụng sử dụng SSR, bạn không nên giả định mọi code đều chạy trong browser.
Ví dụ không an toàn:
const theme = localStorage.getItem('theme')Nếu code được thực thi trong môi trường server, localStorage không tồn tại.
Cần phân biệt:
- Code chạy trên server.
- Code chạy trên client.
- Code chạy trong quá trình hydration.
- Code chạy sau khi component mount.
Ngoài ra, khi dùng event listener hoặc subscription, hãy cleanup:
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})Lifecycle hooks không chỉ để chạy code đúng thời điểm, mà còn để quản lý vòng đời tài nguyên.
24. Ưu tiên events, slots và composables thay vì truyền function props quá mức
Truyền function qua props đôi khi hữu ích:
<DataTable :formatter="formatValue" />Nhưng nếu component bắt đầu nhận quá nhiều callback:
<ComplexComponent
:on-save="handleSave"
:on-delete="handleDelete"
:on-hover="handleHover"
:on-format="formatValue"
:on-validate="validate"
/>thì API của component có thể trở nên khó hiểu.
Trong nhiều trường hợp, bạn nên cân nhắc:
- Emit events.
- Slots.
- Scoped slots.
- Composables.
- Dependency injection.
- Tách component nhỏ hơn.
Ví dụ:
<DataTable
@save="handleSave"
@delete="handleDelete"
/>Slots cũng rất phù hợp khi component cần cho phép tùy biến phần hiển thị:
<DataTable>
<template #empty>
Chưa có dữ liệu.
</template>
</DataTable>Function props không bị cấm, nhưng nên được sử dụng khi nó thực sự làm API rõ ràng hơn.
25. Không mutate props trực tiếp
Props là dữ liệu đi từ component cha xuống component con. Component con không nên trực tiếp thay đổi props.
Không nên:
const props = defineProps<{
count: number
}>()
props.count++Thay vào đó, hãy:
- Emit event lên component cha.
- Dùng
v-model. - Tạo local state nếu cần chỉnh sửa độc lập.
- Dùng computed nếu chỉ cần biến đổi giá trị để hiển thị.
Luồng dữ liệu nên rõ ràng:
Parent state
↓
Props
↓
Child component
↓
Emit event
↓
Parent cập nhật stateLuồng dữ liệu một chiều giúp ứng dụng dễ debug và dễ bảo trì hơn.
26. Sử dụng dynamic classes thay vì tạo quá nhiều component trạng thái
Nhiều giao diện có các trạng thái như:
- Active.
- Disabled.
- Selected.
- Loading.
- Error.
- Success.
- Expanded.
- Collapsed.
Không phải trạng thái nào cũng cần một component riêng.
Bạn có thể dùng dynamic class:
<button
:class="[
'button',
{
'button--active': active,
'button--disabled': disabled,
'button--loading': loading,
},
]"
>
{{ label }}
</button>Hoặc object syntax:
<div
:class="{
active: isActive,
disabled: isDisabled,
}"
>
Nội dung
</div>Khi class logic trở nên phức tạp, hãy tách thành computed:
const buttonClasses = computed(() => ({
button: true,
'button--active': active.value,
'button--disabled': disabled.value,
'button--loading': loading.value,
}))Dynamic classes giúp giữ component linh hoạt mà không cần tạo ra quá nhiều biến thể component chỉ khác nhau ở một vài trạng thái giao diện.
Những điều quan trọng hơn cả 26 mẹo
Các mẹo trên giúp tiết kiệm thời gian ở cấp độ component. Nhưng để phát triển Vue hiệu quả trong năm 2026, bạn còn cần chú ý đến những vấn đề lớn hơn.
1. Bắt đầu dự án bằng công cụ hiện đại
Với ứng dụng Vue mới, Vite thường là lựa chọn mặc định phù hợp.
Bạn nên cân nhắc:
- Vue 3.
- Vite.
- TypeScript.
- Vue Official extension.
vue-tsc.- Vitest.
- Pinia nếu cần global state.
- Vue Router nếu ứng dụng có nhiều route.
- ESLint hoặc Biome tùy quy ước dự án.
- Vue Devtools.
Vue CLI hiện ở trạng thái Maintenance Mode; các dự án mới nên sử dụng công cụ dựa trên Vite và create-vue.
2. Chọn framework phù hợp với loại ứng dụng
Không phải ứng dụng nào cũng chỉ cần Vue + Vite.
Bạn có thể cân nhắc:
- Vue + Vite cho SPA hoặc frontend độc lập.
- Nuxt cho SSR, routing tích hợp, server routes và full-stack Vue.
- VitePress cho tài liệu và documentation.
- Astro khi nội dung là trọng tâm và Vue chỉ được dùng cho các interactive island.
- Các framework khác nếu dự án có yêu cầu đặc biệt.
Không nên chọn framework chỉ vì nó phổ biến. Hãy chọn dựa trên:
- Có cần SSR không?
- Có cần SEO không?
- Có cần server routes không?
- Có cần content-driven website không?
- Có cần SPA thuần không?
- Có cần triển khai đơn giản không?
- Có cần tối ưu JavaScript gửi xuống trình duyệt không?
3. TypeScript không chỉ để tránh lỗi kiểu dữ liệu
TypeScript giúp:
- Props rõ ràng.
- Emits có contract.
- API response có kiểu.
- Store dễ sử dụng.
- Refactor an toàn hơn.
- IDE autocomplete tốt hơn.
- Giảm lỗi khi đổi tên hoặc thay đổi cấu trúc dữ liệu.
Tuy nhiên, TypeScript chỉ hữu ích nếu bạn không liên tục né tránh nó bằng:
anyhoặc:
as anyMột codebase Vue tốt nên cố gắng mô hình hóa domain rõ ràng thay vì chỉ thêm type để làm trình biên dịch im lặng.
4. Viết test cho logic quan trọng
Không phải mọi component đều cần test chi tiết từng dòng. Nhưng những logic quan trọng nên được kiểm thử:
- Validation.
- Tính toán giá trị.
- Composables.
- Store.
- Permission.
- Form submission.
- Các trạng thái loading/error.
- Những bug từng xảy ra.
- Component quan trọng trong sản phẩm.
Vitest thường phù hợp với hệ sinh thái Vite và các dự án Vue hiện đại.
5. Đừng tối ưu hiệu suất trước khi đo lường
Không phải cứ dùng:
shallowRef.markRaw.v-memo.- Lazy loading.
- Virtual list.
- Dynamic import.
- Web Worker.
là ứng dụng sẽ tốt hơn.
Trước tiên hãy xác định vấn đề:
- Bundle quá lớn?
- Render quá nhiều?
- Watcher chạy quá thường xuyên?
- API chậm?
- DOM quá lớn?
- Component mount quá lâu?
- Hydration chậm?
- Network request bị lặp?
- Memory leak?
Tối ưu hiệu suất nên dựa trên đo lường, không phải dựa trên cảm giác.
Bảng chuyển đổi tư duy từ Vue cũ sang Vue hiện đại
| Tư duy cũ | Cách tiếp cận phù hợp hơn trong Vue 2026 |
|---|---|
| Vuex cho mọi state | Composables hoặc Pinia tùy phạm vi state |
| Ép component render lại | Kiểm tra reactivity và thiết kế state |
| Một root node bắt buộc | Có thể dùng nhiều root node |
| Filters | Computed hoặc utility functions |
| Tự viết validation mọi nơi | Schema validation hoặc form library khi cần |
| Dùng Axios mặc định | Chọn fetch hoặc Axios theo nhu cầu |
| Dùng watcher cho mọi thứ | Computed cho derived state, watch cho side effect |
| Truyền nhiều callback props | Events, slots và composables |
| Mutate props | Emit event hoặc v-model |
Tự quản lý modelValue dài dòng | defineModel() trong Vue 3.4+ |
| Dùng JavaScript cho hover | CSS cho tương tác trình bày đơn giản |
| Component làm mọi việc | Component contract rõ ràng và tách trách nhiệm |
| Chỉ quan tâm success state | Loading, error, empty và retry state |
| Dùng framework theo thói quen | Chọn Vite, Nuxt, VitePress hoặc Astro theo mục tiêu |
| Chỉ kiểm tra bằng mắt | Devtools, type checking và automated tests |
Kết luận
Tiết kiệm thời gian khi phát triển Vue không chỉ là biết thêm vài thủ thuật cú pháp. Quan trọng hơn là biết khi nào không nên viết thêm code.
Một developer Vue hiệu quả trong năm 2026 nên:
- Hiểu rõ reactivity.
- Phân biệt local state, shared state và URL state.
- Dùng
computedcho dữ liệu dẫn xuất. - Dùng
watchcho side effect. - Không mutate props.
- Không ép render lại khi chưa hiểu nguyên nhân.
- Thiết kế component contract rõ ràng.
- Tách logic thành composables khi cần.
- Sử dụng Pinia có chọn lọc.
- Xử lý đầy đủ loading, error và empty state.
- Dùng TypeScript để mô hình hóa dữ liệu.
- Dùng Vite và các công cụ hiện đại.
- Viết test cho logic quan trọng.
- Đo lường trước khi tối ưu hiệu suất.
- Không biến framework thành nơi chứa mọi loại logic.
Vue giúp bạn xây dựng giao diện nhanh. Nhưng kiến trúc tốt mới giúp bạn không phải xây lại chúng.