MediaPipe với Vue: Ưu và Nhược điểm
Mục tiêu bài học
- Hiểu ưu điểm khi dùng MediaPipe trong Vue
- Nắm rõ nhược điểm và cách giải quyết
- So sánh với React và frameworks khác
- Đưa ra recommendations cho dự án
Ưu điểm (Pros)
1. Reactivity System mạnh mẽ
Vue 3 Reactivity giúp quản lý state dễ dàng:
typescript
// Tự động update UI khi state thay đổi
const faceDetected = ref(false);
const landmarksCount = ref(0);
wrapper.onResults((results) => {
// Update state → UI tự động re-render
faceDetected.value = !!results.multiFaceLandmarks?.length;
landmarksCount.value = results.multiFaceLandmarks?.[0]?.length ?? 0;
});Lợi ích:
- Không cần thao tác DOM thủ công
- Đồng bộ trạng thái tự động
- Code sạch hơn
2. Composition API linh hoạt
Dễ dàng tách logic thành composables:
typescript
// composables/useFaceDetection.ts
export function useFaceDetection() {
const isReady = ref(false);
const faceDetected = ref(false);
const wrapper = shallowRef<MediaPipeFaceMeshWrapper | null>(null);
async function initialize() {
wrapper.value = new MediaPipeFaceMeshWrapper();
await wrapper.value.initialize();
isReady.value = true;
}
function cleanup() {
wrapper.value?.destroy();
wrapper.value = null;
}
return {
isReady,
faceDetected,
initialize,
cleanup,
};
}
// Sử dụng trong component
const { isReady, faceDetected, initialize, cleanup } = useFaceDetection();Lợi ích:
- Code có thể tái sử dụng
- Dễ kiểm thử
- Tách biệt các mối quan tâm
3. Template syntax trực quan
vue
<template>
<!-- Conditional rendering dễ đọc -->
<div v-if="!isReady">
<Lucide icon="Loader" class="animate-spin" />
<p>{{ t("locale.loading_model") }}</p>
</div>
<div v-else-if="!faceDetected">
<Lucide icon="AlertCircle" />
<p>{{ t("locale.no_face_detected") }}</p>
</div>
<div v-else class="success">
<Lucide icon="CheckCircle" />
<p>{{ t("locale.face_detected") }}</p>
<BtnBase @click="capture">{{ t("locale.capture") }}</BtnBase>
</div>
</template>Lợi ích:
- Template rõ ràng và dễ bảo trì
- Logic UI tách biệt khỏi logic nghiệp vụ
- Dễ hiểu
4. TypeScript Integration tốt
typescript
// Type-safe refs
const videoRef = ref<HTMLVideoElement>();
const wrapper = shallowRef<MediaPipeFaceMeshWrapper | null>(null);
// Type-safe props
interface Props {
onCapture: (image: string) => void;
minConfidence?: number;
}
const props = withDefaults(defineProps<Props>(), {
minConfidence: 0.5,
});5. DevTools mạnh mẽ
Vue DevTools giúp debug dễ dàng:
- Kiểm tra trạng thái component
- Theo dõi reactivity
- Dòng thời gian của lifecycle hooks
Nhược điểm (Cons)
1. ⚠️ Proxy Reactivity Issues
Vấn đề lớn nhất: Vue Proxy không tương thích với WASM.
typescript
// ❌ KHÔNG hoạt động
const wrapper = ref(new MediaPipeFaceMeshWrapper());
const video = ref<HTMLVideoElement>();
// Lỗi: Vue wrap trong Proxy → WASM crash
await wrapper.value.faceMesh.send({ image: video.value });Giải pháp: Dùng shallowRef hoặc không dùng ref:
typescript
// ✅ Solution 1: shallowRef (shallow reactivity)
const wrapper = shallowRef<MediaPipeFaceMeshWrapper | null>(null);
// ✅ Solution 2: Plain variable (recommended)
let wrapper: MediaPipeFaceMeshWrapper | null = null;Tác động:
- Cần hiểu sâu về Vue reactivity
- Dễ mắc lỗi cho người mới
- Phải tài liệu hóa rõ ràng
2. ⚠️ Vite Build Complexity
MediaPipe export không consistent giữa dev và production:
typescript
// Dev mode: OK
import { FaceMesh } from '@mediapipe/face_mesh';
const faceMesh = new FaceMesh(...);
// Production build: Lỗi!
// TypeError: FaceMesh is not a constructorGiải pháp: Custom Vite plugin + wrapper:
typescript
// vite.config.ts
import mediaPipePlugin from "./mediaPipePlugin";
export default defineConfig({
build: {
rollupOptions: {
plugins: [mediaPipePlugin()],
},
commonjsOptions: {
transformMixedEsModules: true,
},
},
optimizeDeps: {
esbuildOptions: {
keepNames: true,
},
},
});Tác động:
- Thiết lập phức tạp hơn React
- Cần duy trì plugin tùy chỉnh
- Debug khó khăn
3. ⚠️ Bundle Size
MediaPipe + Vue = large bundle:
dist/
├── mediapipe-face-mesh.js (~7MB) ← MediaPipe WASM
├── vendor.js (~500KB) ← Vue + deps
├── index.js (~200KB) ← App code
└── Total: ~7.7MBGiải pháp: Code splitting + lazy loading:
typescript
// Lazy load MediaPipe khi cần
const { MediaPipeFaceMeshWrapper } = await import(
"@/utils/ekyc/MediaPipeFaceMeshWrapper"
);Tác động:
- Tải ban đầu chậm (lần đầu)
- Cần CDN hoặc chiến lược cache
4. ⚠️ toRaw() Confusion
Developer thường nhầm lẫn khi nào cần toRaw():
typescript
// ❓ Khi nào cần toRaw()?
// KHÔNG cần: DOM element từ ref đã là raw
const video = ref<HTMLVideoElement>();
wrapper.setVideoElement(video.value); // ✅ OK
// CẦN: Nếu object được wrap trong reactive()
const config = reactive({ threshold: 0.5 });
sendToWasm(toRaw(config)); // ✅ Cần toRawTác động:
- Đường cong học tập cao hơn
- Dễ gây nhầm lẫn cho team mới
5. ⚠️ Lifecycle Management
Vue có nhiều lifecycle hooks → dễ nhầm lẫn:
typescript
// ❓ Initialize ở đâu?
onMounted(); // ✅ Tốt: DOM đã ready
onBeforeMount(); // ❌ Sớm quá: video chưa có
created(); // ❌ Options API (không dùng)
// ❓ Cleanup ở đâu?
onBeforeUnmount(); // ✅ Tốt: trước khi unmount
onUnmounted(); // ⚠️ Muộn: DOM đã mấtTác động:
- Phải hiểu rõ lifecycle
- Dễ rò rỉ bộ nhớ nếu không dọn dẹp đúng cách
So sánh với React
| Aspect | Vue 3 | React |
|---|---|---|
| Reactivity | Automatic (Proxy) | Manual (setState) |
| WASM Integration | ⚠️ Cần wrapper | ✅ Direct |
| Build Setup | ⚠️ Complex (Vite plugin) | ✅ Simple |
| TypeScript | ✅ Excellent | ✅ Excellent |
| Learning Curve | ⚠️ Higher (proxy issues) | ✅ Lower |
| Performance | ✅ Fast | ✅ Fast |
| Bundle Size | Similar | Similar |
React Example (for comparison)
typescript
// React: Không có proxy issues
import { FaceMesh } from '@mediapipe/face_mesh';
function FaceDetection() {
const [faceDetected, setFaceDetected] = useState(false);
const videoRef = useRef<HTMLVideoElement>(null);
const faceMeshRef = useRef<FaceMesh | null>(null);
useEffect(() => {
const faceMesh = new FaceMesh({...});
faceMeshRef.current = faceMesh;
faceMesh.onResults((results) => {
// Trực tiếp, không có Proxy
setFaceDetected(!!results.multiFaceLandmarks?.length);
});
// Send trực tiếp
faceMesh.send({ image: videoRef.current });
return () => faceMesh.close();
}, []);
return <video ref={videoRef} />;
}React pros: Không cần wrapper phức tạp
Vue pros: Template và reactivity tốt hơn
Khuyến nghị
Khi nào NÊN dùng Vue + MediaPipe
✅ Dùng khi:
- Team đã quen thuộc với Vue
- Có thời gian thiết lập Vite plugin đúng cách
- Cần UI/UX phức tạp (Vue template tốt hơn)
- Cần i18n tích hợp sẵn (vue-i18n)
Khi nào KHÔNG NÊN dùng Vue
❌ Không dùng khi:
- Cần tích hợp các thư viện WASM khác (các vấn đề tương tự)
- Team mới, chưa hiểu Vue reactivity
- Cần triển khai nhanh, không có thời gian debug build
- Prototype nhanh (React đơn giản hơn)
Thực hành tốt nhất cho Vue + MediaPipe
- Luôn dùng wrapper class
typescript
// ✅ Tách biệt WASM trong wrapper
let wrapper: MediaPipeFaceMeshWrapper | null = null;
// ❌ Không dùng trực tiếp trong Vue component
const faceMesh = ref(new FaceMesh(...)); // Lỗi!- Tài liệu hóa rõ ràng
typescript
/**
* QUAN TRỌNG: wrapper KHÔNG được bọc trong ref/reactive!
* MediaPipe WASM không tương thích với Vue Proxy.
*/
let wrapper: MediaPipeFaceMeshWrapper | null = null;- Kiểm thử kỹ build production
bash
# Luôn kiểm thử build production
npm run build-production
npm run preview:proxy
# Kiểm tra:
# - FaceMesh có tải được không
# - Camera có hoạt động không
# - Hiệu suất có ổn định không- Giám sát kích thước bundle
bash
# Phân tích bundle
npm run build-production -- --analyze
# Kiểm tra kích thước chunk
ls -lh dist/assets/Tổng kết
| Khía cạnh | Rating | Note |
|---|---|---|
| Development Experience | ⭐⭐⭐⭐ | Vue template và reactivity tuyệt vời |
| Integration Complexity | ⭐⭐ | Cần wrapper + Vite plugin |
| Build Reliability | ⭐⭐⭐ | Ổn định sau khi setup đúng |
| Performance | ⭐⭐⭐⭐ | Tương đương React |
| Maintainability | ⭐⭐⭐ | Cần document kỹ về wrapper |
| Team Onboarding | ⭐⭐ | Learning curve cao |
Tổng thể: Vue + MediaPipe khả thi nhưng cần:
- ✅ Setup cẩn thận
- ✅ Document đầy đủ
- ✅ Team hiểu Vue reactivity
- ✅ Test kỹ production builds