คำถามสัมภาษณ์ Vue.js: 25 ข้อสำคัญเพื่อคว้างาน
เตรียมสัมภาษณ์ Vue.js ด้วย 25 คำถามสำคัญ ตั้งแต่ระบบ reactivity ถึง composables เพื่อพิชิตการสัมภาษณ์ครั้งต่อไป

การสัมภาษณ์ Vue.js วัดผลมากกว่าแค่ไวยากรณ์ของเฟรมเวิร์ก ผู้สรรหาต้องการเห็นความเข้าใจระบบ reactivity ความสามารถในการจัดระเบียบโค้ดด้วย Composition API และทักษะการแก้ปัญหาด้านประสิทธิภาพและสถาปัตยกรรมที่เกิดขึ้นจริง
ทุกคำถามมาพร้อมคำตอบโดยละเอียดและตัวอย่างโค้ด สำหรับการสัมภาษณ์ทางเทคนิค การฝึกอธิบายแนวคิดออกเสียงเหมือนอยู่ในห้องสัมภาษณ์จริงจะช่วยได้มาก
คำถามพื้นฐานเกี่ยวกับ Vue.js
1. ref และ reactive ใน Vue 3 ต่างกันอย่างไร
คำถามนี้ทดสอบความเข้าใจระบบ reactivity ซึ่งเป็นหัวใจของ Vue 3 ความแตกต่างหลักอยู่ที่ประเภทข้อมูลและรูปแบบการเข้าถึง
ref สร้างการอ้างอิงแบบ reactive สำหรับค่าพื้นฐาน (string, number, boolean) และต้องใช้ .value เพื่อเข้าถึงค่าภายในสคริปต์ ส่วน reactive สร้าง proxy แบบ reactive สำหรับ object และเข้าถึงคุณสมบัติได้โดยตรง
// ตัวอย่างเปรียบเทียบ ref กับ reactive
import { ref, reactive } from 'vue'
// ref: for primitives
// Requires .value in the script
const count = ref(0)
count.value++ // Access with .value
// reactive: for complex objects
// Direct property access
const user = reactive({
name: 'Alice',
age: 25
})
user.age++ // No .value needed
// Warning: reactive loses reactivity if reassigned
// user = { name: 'Bob', age: 30 } // ❌ Breaks reactivity
Object.assign(user, { name: 'Bob', age: 30 }) // ✅ Correctหลักทั่วไป: ใช้ ref กับค่าธรรมดา และ reactive กับ object ที่มีหลายคุณสมบัติเชื่อมโยงกัน
2. ระบบ reactivity ของ Vue 3 ทำงานอย่างไร
Vue 3 ใช้ Proxy ของ JavaScript (ES6) เพื่อดักจับการดำเนินการบน object แบบ reactive ต่างจาก Vue 2 ที่ใช้ Object.defineProperty วิธีนี้สามารถตรวจจับการเพิ่มและลบคุณสมบัติแบบไดนามิก
// Simplified demonstration of the reactivity principle
// Vue uses Proxies to track dependencies
const handler = {
// Intercept reading
get(target, key, receiver) {
track(target, key) // Register the dependency
return Reflect.get(target, key, receiver)
},
// Intercept writing
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key) // Trigger updates
return result
}
}
// Creating a reactive proxy
const reactiveObject = new Proxy(originalObject, handler)ข้อดีของ Proxy ได้แก่ การตรวจจับคุณสมบัติใหม่ การรองรับ Map และ Set รวมถึงประสิทธิภาพที่ดีขึ้นกับ object ขนาดใหญ่
3. อธิบายความต่างระหว่าง computed และ watch
computed และ watch ตอบโจทย์การจัดการ reactivity ที่ต่างกัน
Computed: คำนวณค่าที่เป็นผลจากข้อมูล reactive อื่น ค่าจะถูก cache ไว้และคำนวณใหม่เฉพาะตอนที่ dependency เปลี่ยน เหมาะกับการแปลงข้อมูล
Watch: รัน side effect เพื่อตอบสนองต่อการเปลี่ยนแปลง ใช้สำหรับการเรียก API การโต้ตอบกับ DOM หรือการดำเนินการแบบอะซิงโครนัส
// Computed vs watch comparison
import { ref, computed, watch } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// computed: derived value with cache
// Recalculates only if firstName or lastName changes
const fullName = computed(() => {
console.log('Computing full name') // Called only once
return `${firstName.value} ${lastName.value}`
})
// Multiple accesses = single execution (cached)
console.log(fullName.value) // "John Doe"
console.log(fullName.value) // No recalculation
// watch: side effect without cache
// Executed on every change
watch(firstName, async (newName, oldName) => {
// Side effect: API call
await saveToServer({ firstName: newName })
console.log(`Name changed from ${oldName} to ${newName}`)
})4. Virtual DOM คืออะไร และ Vue ใช้งานอย่างไร
Virtual DOM คือการแทน DOM จริงในรูปแบบ JavaScript ที่เบา Vue เก็บโครงสร้างต้นไม้เสมือนไว้ในหน่วยความจำและคำนวณส่วนต่าง (diffing) ระหว่างสถานะเก่ากับใหม่ เพื่ออัปเดตเฉพาะสิ่งที่จำเป็นบน DOM จริง
// Conceptual representation of the Virtual DOM
// Vue creates this structure internally
const vnode = {
type: 'div',
props: {
class: 'container',
id: 'app'
},
children: [
{
type: 'h1',
props: {},
children: 'Title'
},
{
type: 'p',
props: {},
children: 'Paragraph content'
}
]
}
// When changes occur, Vue compares vnodes
// and updates only the modified elementsการเพิ่มประสิทธิภาพของ Vue 3 ได้แก่ การ hoist โหนดสแตติก patch flag เพื่อระบุประเภทการเปลี่ยนแปลง และ tree-shaking ในคอมไพเลอร์
5. จัดการการสื่อสารระหว่างคอมโพเนนต์ที่ไม่ได้สัมพันธ์โดยตรงอย่างไร
มีหลายรูปแบบสำหรับให้คอมโพเนนต์ที่ไม่ได้มีความสัมพันธ์ parent-child โดยตรงคุยกัน
// Solution 1: Event Bus (small applications)
// eventBus.js
import { ref } from 'vue'
const bus = ref(new Map())
export function useEventBus() {
// Emit an event
const emit = (event, payload) => {
const callbacks = bus.value.get(event) || []
callbacks.forEach(cb => cb(payload))
}
// Listen to an event
const on = (event, callback) => {
if (!bus.value.has(event)) {
bus.value.set(event, [])
}
bus.value.get(event).push(callback)
}
return { emit, on }
}// Solution 2: Provide/Inject for nested components
// Ancestor
import { provide, ref } from 'vue'
const sharedState = ref('shared value')
provide('stateKey', sharedState)
// Descendant (any level)
import { inject } from 'vue'
const state = inject('stateKey')สำหรับแอปพลิเคชันที่ซับซ้อนมากขึ้น Pinia ยังเป็นทางเลือกที่แนะนำสำหรับการจัดการ state แบบ global
คำถามเกี่ยวกับ Composition API
6. Composition API มีข้อได้เปรียบอย่างไรเมื่อเทียบกับ Options API
Composition API มีจุดเด่นเชิงโครงสร้างหลายอย่างเมื่อเทียบกับ Options API ดั้งเดิม
จัดระเบียบตามฟีเจอร์: โค้ดที่เกี่ยวข้องกับฟีเจอร์เดียวกันถูกจัดวางอยู่ด้วยกัน ในขณะที่ Options API จะแยกตามประเภท (data, methods, computed)
นำกลับมาใช้ใหม่ผ่าน composables: ดึงตรรกะออกเป็นฟังก์ชันที่นำกลับมาใช้ได้ง่าย
รองรับ TypeScript ดีขึ้น: การ infer ประเภทเป็นไปอย่างเป็นธรรมชาติ ไม่ต้องใช้ decorator
// Options API: code fragmented by type
export default {
data() {
return {
searchQuery: '',
results: []
}
},
computed: {
hasResults() {
return this.results.length > 0
}
},
methods: {
async search() {
this.results = await fetchResults(this.searchQuery)
}
},
watch: {
searchQuery: 'search'
}
}
// Composition API: code grouped by feature
import { ref, computed, watch } from 'vue'
export function useSearch() {
const searchQuery = ref('')
const results = ref([])
const hasResults = computed(() => results.value.length > 0)
const search = async () => {
results.value = await fetchResults(searchQuery.value)
}
watch(searchQuery, search)
return { searchQuery, results, hasResults, search }
}7. สร้าง composable ที่นำกลับมาใช้ใหม่ได้อย่างไร
composable คือฟังก์ชันที่ห่อตรรกะ reactive ไว้ ข้อตกลงทั่วไป: ใช้คำนำหน้า use คืน object ที่มีทั้ง state และ method และจัดการ cleanup
import { ref, watch } from 'vue'
// Composable to synchronize state with localStorage
export function useLocalStorage(key, defaultValue) {
// Retrieve initial value from localStorage
const storedValue = localStorage.getItem(key)
const data = ref(
storedValue ? JSON.parse(storedValue) : defaultValue
)
// Synchronize changes to localStorage
watch(
data,
(newValue) => {
if (newValue === null) {
localStorage.removeItem(key)
} else {
localStorage.setItem(key, JSON.stringify(newValue))
}
},
{ deep: true } // Observe nested objects
)
return data
}
// Usage in a component
const theme = useLocalStorage('theme', 'light')
const userPrefs = useLocalStorage('prefs', { notifications: true })composable ใช้รูปแบบ useXxx เพื่อบ่งบอกถึงคุณสมบัติที่นำกลับมาใช้ได้ ข้อตกลงนี้ทำให้โค้ดอ่านง่ายและช่วยให้สังเกต dependency แบบ reactive ได้ง่ายขึ้น
8. อธิบาย watchEffect เทียบกับ watch
watchEffect กับ watch ต่างก็ตอบสนองต่อการเปลี่ยนแปลง แต่แนวทางแตกต่างกัน
watchEffect: ทำงานทันทีและรันใหม่อัตโนมัติเมื่อ dependency แบบ reactive เปลี่ยน การติดตาม dependency เป็นไปโดยอัตโนมัติ
watch: คอยสังเกตแหล่งข้อมูลที่ระบุและให้ค่าเก่ากับค่าใหม่ มีอำนาจในการกำหนดเวลาที่จะถูกกระตุ้นมากกว่า
// watchEffect vs watch comparison
import { ref, watch, watchEffect } from 'vue'
const userId = ref(1)
const userData = ref(null)
// watchEffect: automatic tracking
// Runs immediately
watchEffect(async () => {
// userId is automatically tracked
const response = await fetch(`/api/users/${userId.value}`)
userData.value = await response.json()
})
// watch: explicit sources with old values
watch(userId, async (newId, oldId) => {
console.log(`User changed from ${oldId} to ${newId}`)
const response = await fetch(`/api/users/${newId}`)
userData.value = await response.json()
}, {
immediate: true // Run immediately like watchEffect
})
// watchEffect with cleanup
watchEffect((onCleanup) => {
const controller = new AbortController()
fetch(`/api/users/${userId.value}`, {
signal: controller.signal
}).then(/* ... */)
// Cleanup: cancel previous request
onCleanup(() => controller.abort())
})9. จัดการ props กับ TypeScript ใน script setup อย่างไร
ไวยากรณ์ <script setup> รองรับ TypeScript โดยตรงผ่าน defineProps และ withDefaults
<script setup lang="ts">
// Interface for props
interface Props {
title: string
count?: number
items?: string[]
onSubmit?: (data: FormData) => void
}
// defineProps with generic typing
// withDefaults for default values
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => [], // Factory for objects/arrays
onSubmit: undefined
})
// Props are automatically typed
console.log(props.title) // string
console.log(props.count) // number
// defineEmits with typing
const emit = defineEmits<{
(e: 'update', value: number): void
(e: 'delete', id: string): void
}>()
// Typed emit usage
const handleUpdate = () => {
emit('update', props.count + 1)
}
</script>พร้อมที่จะพิชิตการสัมภาษณ์ Vue.js / Nuxt.js แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
คำถามด้านประสิทธิภาพ
10. มีเทคนิคการเพิ่มประสิทธิภาพอะไรบ้าง
Vue 3 มีกลไกหลายอย่างในการเพิ่มประสิทธิภาพ
<template>
<div v-once>
<!-- This content will never be re-rendered -->
<ComplexStaticComponent />
</div>
</template>
// 2. v-memo: conditional memoization
<template>
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.selected]">
<!-- Re-renders only if id or selected changes -->
{{ item.name }}
</div>
</template>
// 3. shallowRef/shallowReactive: shallow reactivity
import { shallowRef, triggerRef } from 'vue'
// Only tracks ref replacement, not internal mutations
const largeList = shallowRef([/* thousands of elements */])
// Force update after mutation
largeList.value.push(newItem)
triggerRef(largeList) // Manually trigger re-renderimport { defineAsyncComponent } from 'vue'
const HeavyComponent = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingSpinner,
delay: 200, // Delay before showing loading
errorComponent: ErrorDisplay,
timeout: 3000
})
// 5. KeepAlive for component caching
<template>
<KeepAlive :include="['Dashboard', 'UserProfile']" :max="10">
<component :is="currentView" />
</KeepAlive>
</template>11. หลีกเลี่ยงการ re-render ที่ไม่จำเป็นได้อย่างไร
การ re-render ที่ไม่จำเป็นส่งผลต่อประสิทธิภาพ มีหลายแนวทางที่ช่วยลดให้น้อยที่สุด
// Problem: function created on each render
<template>
<!-- ❌ New function on every render -->
<ChildComponent @click="() => handleClick(item.id)" />
</template>
// Solution: use a method or ref
<script setup>
const handleItemClick = (id) => {
// Processing logic
}
</script>
<template>
<!-- ✅ Stable reference -->
<ChildComponent @click="handleItemClick(item.id)" />
</template>// Using computed for expensive calculations
import { computed } from 'vue'
// ❌ Recalculated on every render
const getFilteredItems = () => {
return items.value.filter(/* complex logic */)
}
// ✅ Cached, recalculated only if items changes
const filteredItems = computed(() => {
return items.value.filter(/* complex logic */)
})12. อธิบาย lazy loading ของคอมโพเนนต์และ route
Lazy loading ทำให้โหลดโค้ดเมื่อจำเป็น ลดขนาด bundle ตอนเริ่มต้น
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
// Immediate loading (main bundle)
component: () => import('@/views/Home.vue')
},
{
path: '/dashboard',
// Separate chunk with custom name
component: () => import(
/* webpackChunkName: "dashboard" */
'@/views/Dashboard.vue'
),
// Lazy loading child routes
children: [
{
path: 'analytics',
component: () => import('@/views/Analytics.vue')
}
]
},
{
path: '/admin',
// Prefetch on link hover
component: () => import('@/views/Admin.vue'),
meta: { prefetch: true }
}
]
})
export default routerคำถามเกี่ยวกับ Vue Router
13. ป้องกัน route ด้วย guard อย่างไร
Navigation guard ใช้ควบคุมการเข้าถึง route
import { createRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const router = createRouter({
routes: [
{
path: '/dashboard',
component: Dashboard,
meta: { requiresAuth: true, roles: ['admin', 'user'] }
}
]
})
// Global guard: checks authentication
router.beforeEach(async (to, from, next) => {
const auth = useAuthStore()
// Public route
if (!to.meta.requiresAuth) {
return next()
}
// Check authentication
if (!auth.isAuthenticated) {
return next({
path: '/login',
query: { redirect: to.fullPath }
})
}
// Check roles if specified
if (to.meta.roles && !to.meta.roles.includes(auth.user.role)) {
return next('/unauthorized')
}
next()
})
// Component-level guard
export default {
beforeRouteEnter(to, from, next) {
// No access to this here
next(vm => {
// Access component instance via vm
vm.loadData()
})
},
beforeRouteLeave(to, from, next) {
// Confirm before leaving if form modified
if (this.hasUnsavedChanges) {
const answer = confirm('Leave without saving?')
next(answer)
} else {
next()
}
}
}14. ส่ง props ให้ route อย่างไร
Vue Router ช่วยให้แยกคอมโพเนนต์ออกจากพารามิเตอร์ของ route ได้
// Route configuration with props
const routes = [
{
path: '/user/:id',
component: UserProfile,
// Boolean mode: passes params as props
props: true
},
{
path: '/search',
component: SearchResults,
// Function mode: custom transformation
props: (route) => ({
query: route.query.q,
page: parseInt(route.query.page) || 1,
filters: route.query.filters?.split(',') || []
})
},
{
path: '/static',
component: StaticPage,
// Object mode: static props
props: { sidebar: true, theme: 'dark' }
}
]<script setup>
// Props are automatically injected
defineProps<{
id: string
}>()
</script>
// SearchResults.vue
<script setup>
defineProps<{
query: string
page: number
filters: string[]
}>()
</script>คำถามเกี่ยวกับ Pinia และการจัดการ state
15. Pinia ต่างจาก Vuex อย่างไร
Pinia คือเครื่องมือจัดการ state อย่างเป็นทางการของ Vue 3 และมาแทน Vuex ด้วย API ที่กระชับ
| คุณสมบัติ | Vuex | Pinia | |---------|------|-------| | Mutation | ต้องมี | ไม่ต้องการ | | โมดูล | ตั้งค่าซับซ้อน | store อิสระ | | TypeScript | รองรับจำกัด | รองรับเต็มในตัว | | API | Options | Composition + Options | | DevTools | รองรับ | รองรับเต็มที่ |
// Pinia Store with Composition API
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
// State
const items = ref([])
const discountCode = ref(null)
// Getters (computed)
const totalItems = computed(() =>
items.value.reduce((sum, item) => sum + item.quantity, 0)
)
const totalPrice = computed(() => {
const subtotal = items.value.reduce(
(sum, item) => sum + item.price * item.quantity, 0
)
return discountCode.value ? subtotal * 0.9 : subtotal
})
// Actions (direct functions)
function addItem(product) {
const existing = items.value.find(i => i.id === product.id)
if (existing) {
existing.quantity++
} else {
items.value.push({ ...product, quantity: 1 })
}
}
function removeItem(productId) {
const index = items.value.findIndex(i => i.id === productId)
if (index > -1) {
items.value.splice(index, 1)
}
}
async function checkout() {
const response = await api.createOrder(items.value)
items.value = []
return response
}
return {
items, discountCode,
totalItems, totalPrice,
addItem, removeItem, checkout
}
})16. ทำให้ state ของ Pinia store คงอยู่ระหว่างเซสชันได้อย่างไร
การ persist ช่วยรักษา state ไว้ระหว่างเซสชันของผู้ใช้
import { watch } from 'vue'
export function createPersistedState(options = {}) {
const {
key = 'pinia',
storage = localStorage,
paths = null
} = options
return ({ store }) => {
// Restore state on startup
const savedState = storage.getItem(`${key}-${store.$id}`)
if (savedState) {
store.$patch(JSON.parse(savedState))
}
// Persist changes
watch(
() => store.$state,
(state) => {
const toSave = paths
? paths.reduce((acc, path) => {
acc[path] = state[path]
return acc
}, {})
: state
storage.setItem(
`${key}-${store.$id}`,
JSON.stringify(toSave)
)
},
{ deep: true }
)
}
}
// main.js
import { createPinia } from 'pinia'
import { createPersistedState } from './plugins/piniaPersistedState'
const pinia = createPinia()
pinia.use(createPersistedState({
key: 'app-state',
paths: ['user', 'preferences'] // Persist only these keys
}))ควรหลีกเลี่ยงการเก็บข้อมูลอ่อนไหว (token, รหัสผ่าน) ใน localStorage สำหรับ token การยืนยันตัวตน ควรใช้คุกกี้แบบ httpOnly จะปลอดภัยกว่า
คำถามขั้นสูง
17. สร้างระบบ plugin ใน Vue ได้อย่างไร
Plugin ใช้ขยาย Vue ด้วยฟีเจอร์ระดับ global
export const AnalyticsPlugin = {
install(app, options = {}) {
const { trackingId, debug = false } = options
// Global injection available in all components
const analytics = {
trackEvent(category, action, label) {
if (debug) {
console.log('Analytics:', { category, action, label })
}
// Logic to send to analytics service
window.gtag?.('event', action, {
event_category: category,
event_label: label
})
},
trackPage(path) {
window.gtag?.('config', trackingId, { page_path: path })
}
}
// Make available via inject
app.provide('analytics', analytics)
// Add global property (discouraged in Composition API)
app.config.globalProperties.$analytics = analytics
// Custom directive for click tracking
app.directive('track', {
mounted(el, binding) {
el.addEventListener('click', () => {
analytics.trackEvent('click', binding.value, el.textContent)
})
}
})
// Automatic route change tracking
app.mixin({
mounted() {
if (this.$route) {
analytics.trackPage(this.$route.path)
}
}
})
}
}
// main.js
import { AnalyticsPlugin } from './plugins/analyticsPlugin'
app.use(AnalyticsPlugin, {
trackingId: 'UA-XXXXX-X',
debug: import.meta.env.DEV
})18. อธิบาย render function และประโยชน์
Render function ให้ความควบคุมการ render อย่างเต็มที่ เหมาะกับคอมโพเนนต์ที่เปลี่ยนแปลงสูง
import { h } from 'vue'
// Functional component with render function
export const DynamicHeading = {
props: {
level: {
type: Number,
default: 1,
validator: (v) => v >= 1 && v <= 6
}
},
setup(props, { slots }) {
// h() creates a vnode
// Arguments: type, props, children
return () => h(
`h${props.level}`,
{ class: 'dynamic-heading' },
slots.default?.()
)
}
}
// Component with complex conditional logic
export const ConditionalWrapper = {
props: ['condition', 'wrapper'],
setup(props, { slots }) {
return () => {
if (props.condition) {
return h(props.wrapper, null, slots.default?.())
}
return slots.default?.()
}
}
}
// Usage
<DynamicHeading :level="2">Level 2 Title</DynamicHeading>
<ConditionalWrapper :condition="isLink" wrapper="a">
Conditional content
</ConditionalWrapper>19. ทดสอบคอมโพเนนต์ Vue ด้วย Vitest อย่างไร
Unit test ใช้ตรวจสอบพฤติกรรมแบบแยกของคอมโพเนนต์
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import Counter from '../Counter.vue'
describe('Counter', () => {
it('displays the initial value', () => {
const wrapper = mount(Counter, {
props: { initialValue: 5 }
})
expect(wrapper.text()).toContain('5')
})
it('increments the value on click', async () => {
const wrapper = mount(Counter)
await wrapper.find('button.increment').trigger('click')
expect(wrapper.text()).toContain('1')
})
it('emits an event when changed', async () => {
const wrapper = mount(Counter)
await wrapper.find('button.increment').trigger('click')
expect(wrapper.emitted('change')).toBeTruthy()
expect(wrapper.emitted('change')[0]).toEqual([1])
})
it('calls the service on submit', async () => {
const mockSubmit = vi.fn()
const wrapper = mount(Counter, {
global: {
provide: {
submitService: mockSubmit
}
}
})
await wrapper.find('form').trigger('submit')
expect(mockSubmit).toHaveBeenCalled()
})
})20. จัดการ error แบบ global ใน Vue ได้อย่างไร
Vue 3 มีกลไกหลายอย่างสำหรับดักจับและจัดการ error
import { createApp } from 'vue'
const app = createApp(App)
// Global handler for component errors
app.config.errorHandler = (err, instance, info) => {
// err: the error
// instance: the component instance
// info: string describing where the error occurred
console.error('Vue error:', err)
console.error('Component:', instance?.$options?.name)
console.error('Info:', info)
// Send to monitoring service
errorTracker.captureException(err, {
component: instance?.$options?.name,
info
})
}
// Handler for warnings (dev only)
app.config.warnHandler = (msg, instance, trace) => {
console.warn('Vue warning:', msg)
}// ErrorBoundary component
<script setup>
import { onErrorCaptured, ref } from 'vue'
const error = ref(null)
// Captures errors from child components
onErrorCaptured((err, instance, info) => {
error.value = {
message: err.message,
component: instance?.$options?.name,
info
}
// Return false to stop propagation
return false
})
const retry = () => {
error.value = null
}
</script>
<template>
<div v-if="error" class="error-boundary">
<h2>An error occurred</h2>
<p>{{ error.message }}</p>
<button @click="retry">Retry</button>
</div>
<slot v-else />
</template>พร้อมที่จะพิชิตการสัมภาษณ์ Vue.js / Nuxt.js แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
คำถามเกี่ยวกับแนวทางที่ดี
21. ควรปฏิบัติตามกฎการตั้งชื่อใดใน Vue
กฎการตั้งชื่อช่วยให้โค้ดอ่านและดูแลรักษาได้ง่ายขึ้น
// Component naming
// PascalCase for files and names
// BaseButton.vue, AppHeader.vue, TheNavbar.vue
// Props: camelCase in JS, kebab-case in template
defineProps<{
userName: string // JS
isActive: boolean // JS
}>()
// <UserCard :user-name="name" :is-active="active" />
// Events: camelCase with action prefix
const emit = defineEmits<{
(e: 'updateValue', value: string): void // ✅
(e: 'submit'): void // ✅
(e: 'value-updated'): void // ❌ Avoid
}>()
// Composables: use prefix
// useAuth.js, useFetch.js, useLocalStorage.js
// Pinia stores: use prefix + Store suffix
// useUserStore, useCartStore, useSettingsStore
// Constants: SCREAMING_SNAKE_CASE
const MAX_RETRY_COUNT = 3
const API_BASE_URL = '/api/v1'22. จัดโครงสร้างโปรเจกต์ Vue ขนาดใหญ่อย่างไร
โครงสร้างแบบโมดูลช่วยให้นำทางและบำรุงรักษาง่ายขึ้น
src/
├── assets/ # Static files
├── components/
│ ├── ui/ # Generic components (Button, Modal)
│ └── common/ # Reusable business components
├── composables/ # Reusable logic
│ ├── useAuth.js
│ └── useFetch.js
├── layouts/ # Page layouts
│ ├── DefaultLayout.vue
│ └── AuthLayout.vue
├── modules/ # Functional modules
│ ├── auth/
│ │ ├── components/
│ │ ├── composables/
│ │ ├── stores/
│ │ └── views/
│ └── dashboard/
├── plugins/ # Vue plugins
├── router/
│ ├── index.js
│ └── guards.js
├── stores/ # Global Pinia stores
├── types/ # TypeScript types
├── utils/ # Pure utilities
└── views/ # Pages/Routes23. เมื่อใดควรใช้ v-if แทน v-show
การเลือกระหว่าง v-if กับ v-show ขึ้นกับความถี่ของการสลับสถานะ
// v-if: low initial cost, expensive toggle
// Removes/adds element from DOM
// Ideal for: rarely modified conditions
<template>
<!-- v-if: component not created if not admin -->
<AdminPanel v-if="user.isAdmin" />
<!-- v-if with v-else-if for multiple conditions -->
<LoadingSpinner v-if="isLoading" />
<ErrorMessage v-else-if="error" :message="error" />
<DataDisplay v-else :data="data" />
</template>
// v-show: higher initial cost, fast toggle
// Uses display: none
// Ideal for: frequent toggles
<template>
<!-- v-show: always rendered, frequent toggle -->
<Tooltip v-show="isHovered">
Contextual information
</Tooltip>
<!-- Accordion menu with frequent toggle -->
<div v-show="isExpanded" class="accordion-content">
{{ content }}
</div>
</template>24. ปรับแต่งลิสต์ด้วย v-for อย่างไร
การปรับแต่งลิสต์เป็นเรื่องสำคัญเมื่อมีจำนวนรายการมาก
// Always use :key with a unique stable identifier
<template>
<!-- ✅ Unique and stable key -->
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
<!-- ❌ Index as key (reordering issues) -->
<li v-for="(item, index) in items" :key="index">
{{ item.name }}
</li>
</template>
// Filtering and sorting: use computed
<script setup>
import { computed } from 'vue'
const sortedAndFilteredItems = computed(() => {
return items.value
.filter(item => item.isActive)
.sort((a, b) => a.name.localeCompare(b.name))
})
</script>
// Virtualization for very long lists
<script setup>
import { useVirtualList } from '@vueuse/core'
const { list, containerProps, wrapperProps } = useVirtualList(
largeList,
{ itemHeight: 50 }
)
</script>
<template>
<div v-bind="containerProps" style="height: 400px; overflow: auto">
<div v-bind="wrapperProps">
<div v-for="item in list" :key="item.data.id">
{{ item.data.name }}
</div>
</div>
</div>
</template>25. อธิบายแพตเทิร์น Renderless component
คอมโพเนนต์แบบ renderless ห่อตรรกะไว้โดยไม่กำหนดโครงสร้าง HTML
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const x = ref(0)
const y = ref(0)
const updatePosition = (event) => {
x.value = event.clientX
y.value = event.clientY
}
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition)
})
// Expose state via slot
defineExpose({ x, y })
</script>
<template>
<!-- Slot with props: parent decides the rendering -->
<slot :x="x" :y="y" />
</template>// Usage: full control over rendering
<template>
<MouseTracker v-slot="{ x, y }">
<div class="cursor-display">
Position: {{ x }}, {{ y }}
</div>
</MouseTracker>
<MouseTracker v-slot="{ x, y }">
<svg>
<circle :cx="x" :cy="y" r="10" fill="red" />
</svg>
</MouseTracker>
</template>แพตเทิร์นนี้แยกตรรกะออกจากการนำเสนออย่างสมบูรณ์ ทำให้นำกลับมาใช้ได้สูงสุด
บทสรุป
คำถามทั้ง 25 ข้อนี้ครอบคลุมแนวคิดสำคัญที่มักถูกประเมินในการสัมภาษณ์ Vue.js:
- ✅ Reactivity:
ref,reactive,computed,watch - ✅ Composition API: composable,
script setup, TypeScript - ✅ ประสิทธิภาพ: lazy loading, virtualization, การปรับแต่ง
- ✅ Vue Router: guard, props, การนำทาง
- ✅ Pinia: store, persistence, action แบบอะซิงโครนัส
- ✅ แนวทางที่ดี: โครงสร้าง ข้อตกลง แพตเทิร์นขั้นสูง
การเตรียมตัวที่มีประสิทธิภาพควรผสานความเข้าใจเชิงทฤษฎีกับการลงมือเขียนโค้ด แต่ละแนวคิดที่กล่าวถึงในที่นี้สามารถนำไปสู่คำถามต่อยอดเชิงลึกในห้องสัมภาษณ์ได้
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แท็ก
แชร์
บทความที่เกี่ยวข้อง

Vue 3 Composition API: คู่มือฉบับสมบูรณ์เพื่อเชี่ยวชาญระบบ Reactivity
เชี่ยวชาญ Vue 3 Composition API ผ่านคู่มือเชิงปฏิบัตินี้ เรียนรู้ ref, reactive, computed, watch และ composables เพื่อสร้างแอปพลิเคชัน Vue ที่มีประสิทธิภาพสูง

Vue 3 Pinia vs Vuex: State Management สมัยใหม่และคำถามสัมภาษณ์ 2026
วิเคราะห์เปรียบเทียบ Pinia กับ Vuex: สถาปัตยกรรม, TypeScript, Composition API, ประสิทธิภาพ, กลยุทธ์การย้าย และคำถามสัมภาษณ์ Vue state management 2026

Nuxt 3: SSR และการสร้างเพจแบบ Static คู่มือฉบับสมบูรณ์
เชี่ยวชาญ SSR และการสร้างเพจแบบ Static ด้วย Nuxt 3 ตั้งแต่ useFetch ไปจนถึง route rules เรียนรู้วิธีเพิ่มประสิทธิภาพแอปพลิเคชัน Vue.js