Vue.js - Pytania Rekrutacyjne dla Frontend Developera [2026]
Przygotowujesz się do rozmowy na stanowisko Vue.js Developer? Vue.js to trzeci najpopularniejszy framework JavaScript, ceniony za prostotę, elastyczność i doskonałą dokumentację. Ten przewodnik zawiera 48 pytań rekrutacyjnych z odpowiedziami - od podstaw po Composition API, Vuex/Pinia i zaawansowane wzorce.
Spis treści
- Podstawowe koncepcje Vue.js
- Komponenty i komunikacja
- Reaktywność i computed properties
- Composition API
- Zarządzanie stanem (Vuex/Pinia)
- Vue Router
- Testowanie i best practices
- Zobacz też
Podstawowe koncepcje Vue.js
Czym jest Vue.js i jakie są jego główne cele?
Odpowiedź w 30 sekund: Vue.js to progresywny framework JavaScript do budowania interfejsów użytkownika. "Progresywny" oznacza, że możesz go używać stopniowo - od prostego widgetu na istniejącej stronie po pełne SPA. Główne cechy: reaktywny system danych, komponentowa architektura, łatwa integracja i doskonała dokumentacja.
Odpowiedź w 2 minuty:
Oto przykład najprostszej aplikacji Vue 3 demonstrującej kluczowe koncepcje frameworka:
// Najprostsza aplikacja Vue 3
import { createApp, ref } from 'vue'
const app = createApp({
setup() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
},
template: `
<button @click="increment">
Kliknięto {{ count }} razy
</button>
`
})
app.mount('#app')
| Cecha | Opis |
|---|---|
| Reaktywność | Automatyczna aktualizacja DOM przy zmianie danych |
| Komponenty | Modularna, wielokrotnie używalna architektura |
| Single File Components | HTML, CSS i JS w jednym pliku .vue |
| Virtual DOM | Efektywne aktualizacje DOM |
| Ekosystem | Vue Router, Pinia/Vuex, Vue DevTools |
Dlaczego Vue vs React/Angular:
- vs React: Vue ma wbudowane rozwiązania (router, stan), React wymaga wyboru bibliotek
- vs Angular: Vue jest prostszy, mniejszy, łatwiejszy do nauki
- Vue idealny dla: szybkich prototypów, małych/średnich projektów, zespołów z różnym doświadczeniem
Jak działają dyrektywy w Vue?
Odpowiedź w 30 sekund:
Dyrektywy to specjalne atrybuty z prefiksem v- dodające reaktywne zachowanie do DOM. Najważniejsze: v-bind (:) wiąże atrybuty, v-on (@) nasłuchuje zdarzeń, v-model tworzy two-way binding, v-if/v-show kontrolują wyświetlanie, v-for iteruje po kolekcjach.
Odpowiedź w 2 minuty:
Poniżej znajdziesz kompletny przegląd wszystkich kluczowych dyrektyw Vue z praktycznymi przykładami użycia:
<template>
<!-- v-bind - wiązanie atrybutów (skrót :) -->
<img :src="imageUrl" :alt="imageAlt">
<a :href="link" :class="{ active: isActive }">Link</a>
<!-- v-on - obsługa zdarzeń (skrót @) -->
<button @click="handleClick">Kliknij</button>
<input @keyup.enter="submit">
<form @submit.prevent="onSubmit">
<!-- v-model - two-way binding -->
<input v-model="username">
<input v-model.trim="email">
<input v-model.number="age" type="number">
<!-- v-if vs v-show -->
<div v-if="isLoggedIn">Witaj!</div> <!-- usuwa z DOM -->
<div v-show="isVisible">Treść</div> <!-- display: none -->
<!-- v-for - iteracja -->
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
<!-- v-for z indeksem -->
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
</template>
| Dyrektywa | Użycie | Uwagi |
|---|---|---|
v-bind (:) |
Dynamiczne atrybuty |
:class, :style mają specjalną składnię |
v-on (@) |
Zdarzenia | Modyfikatory: .prevent, .stop, .once
|
v-model |
Formularze | Modyfikatory: .lazy, .trim, .number
|
v-if |
Warunkowe renderowanie | Usuwa element z DOM |
v-show |
Pokazywanie/ukrywanie | Tylko display: none
|
v-for |
Iteracja | Zawsze używaj :key! |
Czym są Single File Components (SFC)?
Odpowiedź w 30 sekund:
SFC to pliki .vue zawierające template (HTML), script (JavaScript) i style (CSS) w jednym pliku. Korzyści: kolokacja kodu, scoped styles (CSS tylko dla komponentu), wsparcie preprocesorów (TypeScript, SCSS, Pug), hot module replacement podczas developmentu.
Odpowiedź w 2 minuty:
Oto przykład kompletnego komponentu Single File Component z TypeScript, pokazujący wszystkie sekcje i najlepsze praktyki:
<!-- UserCard.vue -->
<script setup lang="ts"></script>
<template>
<div class="user-card" @click="toggleExpand">
<img v-if="avatar" :src="avatar" :alt="name">
<span v-else class="initials">{{ initials }}</span>
<h3>{{ name }}</h3>
<p v-show="isExpanded">{{ email }}</p>
</div>
</template>
<style scoped>
/* scoped - style tylko dla tego komponentu */
.user-card {
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
cursor: pointer;
}
.user-card:hover {
background: #f5f5f5;
}
.initials {
width: 40px;
height: 40px;
background: #4a90d9;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
</style>
<script setup> to syntactic sugar dla Composition API:
- Zmienne są automatycznie dostępne w template
- Nie trzeba
returnaniexport default - Lepsze wsparcie TypeScript
- Mniejszy boilerplate
Komponenty i komunikacja
Jak przekazywać dane między komponentami?
Odpowiedź w 30 sekund: Props down, events up: rodzic przekazuje dane przez props, dziecko komunikuje się przez emitowanie zdarzeń. Dla głęboko zagnieżdżonych komponentów: provide/inject. Dla globalnego stanu: Pinia/Vuex. Unikaj modyfikowania props bezpośrednio w dziecku!
Odpowiedź w 2 minuty:
Poniżej znajdziesz kompletny przykład komunikacji rodzic-dziecko zgodnie z zasadą "props down, events up":
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const message = ref('Hello from parent')
const count = ref(0)
const handleIncrement = (value) => {
count.value += value
}
</script>
<template>
<!-- Props down -->
<ChildComponent
:message="message"
:count="count"
@increment="handleIncrement"
/>
</template>
<!-- ChildComponent.vue -->
<script setup>
// Definiowanie props
const props = defineProps({
message: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
})
// Definiowanie emitów
const emit = defineEmits(['increment'])
const increment = () => {
// Events up - NIE modyfikuj props!
emit('increment', 1)
}
</script>
<template>
<div>
<p>{{ message }}</p>
<p>Count: {{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>
Przepływ danych:
┌─────────────────────────────────────────┐
│ Parent │
│ ┌──────────────────────────────────┐ │
│ │ message, count (reactive state) │ │
│ └──────────────────────────────────┘ │
│ │ props ▲ events │
│ ▼ │ │
│ ┌──────────────────────────────────┐ │
│ │ Child │ │
│ │ Odbiera props, emituje eventy │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
Czym są sloty i jak ich używać?
Odpowiedź w 30 sekund: Sloty pozwalają rodzicowi wstrzykiwać treść do komponentu potomnego. Default slot dla głównej treści, named slots dla wielu obszarów, scoped slots gdy dziecko przekazuje dane do rodzica. Używane do tworzenia elastycznych, wielokrotnie używalnych komponentów (karty, modale, layouty).
Odpowiedź w 2 minuty:
Oto przykład elastycznego komponentu karty wykorzystującego różne typy slotów:
<!-- Card.vue - komponent z slotami -->
<template>
<div class="card">
<!-- Named slot: header -->
<header v-if="$slots.header">
<slot name="header"></slot>
</header>
<!-- Default slot -->
<main>
<slot>Domyślna treść gdy brak slotu</slot>
</main>
<!-- Named slot: footer -->
<footer v-if="$slots.footer">
<slot name="footer"></slot>
</footer>
</div>
</template>
<!-- Użycie Card.vue -->
<template>
<Card>
<template #header>
<h2>Tytuł karty</h2>
</template>
<!-- Default slot -->
<p>To jest główna treść karty.</p>
<template #footer>
<button>Zapisz</button>
<button>Anuluj</button>
</template>
</Card>
</template>
Scoped Slots - gdy dziecko przekazuje dane do rodzica:
<!-- List.vue -->
<script setup>
defineProps(['items'])
</script>
<template>
<ul>
<li v-for="item in items" :key="item.id">
<!-- Przekazanie item do rodzica -->
<slot :item="item" :index="index">
{{ item.name }}
</slot>
</li>
</ul>
</template>
<!-- Użycie z scoped slot -->
<template>
<List :items="users">
<template #default="{ item, index }">
<strong>{{ index + 1 }}.</strong>
{{ item.name }} - {{ item.email }}
</template>
</List>
</template>
Reaktywność i computed properties
Jak działa system reaktywności w Vue 3?
Odpowiedź w 30 sekund:
Vue 3 używa JavaScript Proxy do śledzenia dostępu i zmian w obiektach reaktywnych. ref() dla prymitywów (wymaga .value), reactive() dla obiektów. Gdy odczytujesz właściwość, Vue rejestruje zależność. Gdy ją zmieniasz, Vue powiadamia zależne efekty (komponenty, watchers, computed).
Odpowiedź w 2 minuty:
Poniżej znajdziesz praktyczne przykłady używania ref() i reactive() wraz z najczęstszymi pułapkami:
import { ref, reactive, computed, watch } from 'vue'
// ref() - dla prymitywów (i obiektów)
const count = ref(0)
console.log(count.value) // 0
count.value++ // Reaktywna zmiana
// reactive() - dla obiektów
const user = reactive({
name: 'Jan',
email: 'jan@example.com'
})
user.name = 'Anna' // Reaktywna zmiana (bez .value!)
// UWAGA: destrukturyzacja traci reaktywność!
const { name } = user // ❌ name nie jest reaktywne
const name = toRef(user, 'name') // ✅ zachowuje reaktywność
Jak działa Proxy w Vue 3:
┌─────────────────────────────────────────────────────┐
│ const state = reactive({ count: 0 }) │
│ │
│ ┌─────────────────┐ │
│ │ Proxy Handler │ │
│ │ ┌───────────┐ │ get() → track dependency │
│ │ │ count: 0 │ │ set() → trigger update │
│ │ └───────────┘ │ │
│ └─────────────────┘ │
│ │
│ Komponent czyta state.count → Vue rejestruje │
│ state.count zmienia się → Vue re-renderuje │
└─────────────────────────────────────────────────────┘
Pułapki reaktywności:
// ❌ Zastąpienie całego obiektu traci reaktywność
let state = reactive({ count: 0 })
state = reactive({ count: 1 }) // Komponenty nie zauważą!
// ✅ Modyfikuj właściwości
state.count = 1
// ❌ Destrukturyzacja prymitywów
const { count } = reactive({ count: 0 }) // count = 0 (nie reaktywne)
// ✅ Użyj toRefs
const { count } = toRefs(state) // count jest ref
Czym są computed properties i jak różnią się od metod?
Odpowiedź w 30 sekund: Computed to wartości obliczane na podstawie innych reaktywnych danych - są cache'owane i przeliczane tylko gdy zależności się zmieniają. Metody wykonują się przy każdym renderowaniu. Używaj computed dla transformacji danych (filtrowanie, sortowanie), metod dla akcji (obsługa zdarzeń).
Odpowiedź w 2 minuty:
Poniższy przykład pokazuje różnicę w działaniu i wydajności między computed properties a metodami:
<script setup>
import { ref, computed } from 'vue'
const items = ref([
{ name: 'Jabłko', price: 2, inStock: true },
{ name: 'Banan', price: 3, inStock: false },
{ name: 'Pomarańcza', price: 4, inStock: true }
])
const searchQuery = ref('')
// COMPUTED - cache'owane, przeliczane tylko gdy zależności się zmieniają
const availableItems = computed(() => {
console.log('Computing availableItems') // Loguje tylko gdy items się zmieni
return items.value.filter(item => item.inStock)
})
const filteredItems = computed(() => {
return availableItems.value.filter(item =>
item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
})
const totalPrice = computed(() => {
return filteredItems.value.reduce((sum, item) => sum + item.price, 0)
})
// METODA - wykonuje się przy KAŻDYM renderowaniu
const getAvailableItems = () => {
console.log('Method called') // Loguje przy każdym renderze!
return items.value.filter(item => item.inStock)
}
</script>
<template>
<input v-model="searchQuery" placeholder="Szukaj...">
<!-- Computed - efektywne -->
<ul>
<li v-for="item in filteredItems" :key="item.name">
{{ item.name }} - {{ item.price }} zł
</li>
</ul>
<p>Suma: {{ totalPrice }} zł</p>
</template>
| Aspekt | Computed | Method |
|---|---|---|
| Cache | Tak | Nie |
| Wywołanie | Automatyczne | Manualne method()
|
| Użycie | Transformacja danych | Akcje, zdarzenia |
| Zależności | Śledzone automatycznie | - |
| W template | {{ computed }} |
{{ method() }} |
Jak używać watcherów?
Odpowiedź w 30 sekund:
Watchery reagują na zmiany reaktywnych danych wykonując side effects (API calls, localStorage, logowanie). watch() dla konkretnych źródeł z dostępem do starej i nowej wartości. watchEffect() automatycznie śledzi wszystkie używane zależności. Używaj watcherów gdy computed nie wystarczy.
Odpowiedź w 2 minuty:
Oto kompleksowy przykład pokazujący różne zastosowania watch() i watchEffect():
<script setup>
import { ref, watch, watchEffect } from 'vue'
const searchQuery = ref('')
const userId = ref(1)
const user = ref(null)
// watch() - obserwuje konkretne źródło
watch(searchQuery, (newValue, oldValue) => {
console.log(`Search changed from "${oldValue}" to "${newValue}"`)
// Debounced API call...
})
// watch z opcjami
watch(userId, async (newId) => {
user.value = await fetchUser(newId)
}, {
immediate: true, // Wykonaj od razu przy montowaniu
deep: false // Głębokie obserwowanie obiektów
})
// watch wielu źródeł
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`Name changed to ${newFirst} ${newLast}`)
})
// watchEffect - automatycznie śledzi zależności
watchEffect(async () => {
// Automatycznie re-runs gdy userId.value się zmieni
const response = await fetch(`/api/users/${userId.value}`)
user.value = await response.json()
})
// Cleanup w watchEffect
watchEffect((onCleanup) => {
const controller = new AbortController()
fetch(`/api/users/${userId.value}`, { signal: controller.signal })
.then(r => r.json())
.then(data => user.value = data)
onCleanup(() => {
controller.abort() // Anuluj poprzedni request
})
})
</script>
watch |
watchEffect |
|---|---|
| Jawne źródła | Automatyczne śledzenie |
| Dostęp do old/new value | Tylko aktualna wartość |
| Lazy (domyślnie) | Immediate |
| Precyzyjne kontrola | Prostsze dla wielu zależności |
Composition API
Czym jest Composition API i dlaczego warto go używać?
Odpowiedź w 30 sekund: Composition API to alternatywa dla Options API w Vue 3. Zamiast organizować kod według opcji (data, methods, computed), grupujesz logikę według funkcjonalności. Korzyści: lepsza organizacja dużych komponentów, łatwe współdzielenie logiki przez composables, pełne wsparcie TypeScript, lepsze tree-shaking.
Odpowiedź w 2 minuty:
Porównajmy ten sam komponent napisany w Options API i Composition API, aby zobaczyć różnicę w organizacji kodu:
<!-- Options API - kod rozproszony -->
<script>
export default {
data() {
return {
// User logic
user: null,
userLoading: false,
// Posts logic
posts: [],
postsLoading: false
}
},
computed: {
// User logic
userFullName() { /* ... */ },
// Posts logic
publishedPosts() { /* ... */ }
},
methods: {
// User logic
async fetchUser() { /* ... */ },
// Posts logic
async fetchPosts() { /* ... */ }
},
mounted() {
this.fetchUser()
this.fetchPosts()
}
}
</script>
<!-- Composition API - logika zgrupowana -->
<script setup>
import { useUser } from './composables/useUser'
import { usePosts } from './composables/usePosts'
// Cała logika użytkownika w jednym miejscu
const { user, userLoading, userFullName, fetchUser } = useUser()
// Cała logika postów w jednym miejscu
const { posts, postsLoading, publishedPosts, fetchPosts } = usePosts()
onMounted(() => {
fetchUser()
fetchPosts()
})
</script>
// composables/useUser.js - reużywalna logika
import { ref, computed, onMounted } from 'vue'
export function useUser(userId) {
const user = ref(null)
const loading = ref(false)
const error = ref(null)
const fullName = computed(() =>
user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
)
async function fetchUser() {
loading.value = true
try {
const response = await fetch(`/api/users/${userId}`)
user.value = await response.json()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
}
return { user, loading, error, fullName, fetchUser }
}
Czym są composables i jak je tworzyć?
Odpowiedź w 30 sekund:
Composables to funkcje enkapsulujące i reużytkujące logikę stateful z Composition API. Konwencja nazewnictwa: use* (np. useMouse, useAuth, useFetch). Zastępują mixiny z Vue 2 - są bardziej jawne, nie mają konfliktów nazw i lepiej typują się w TypeScript.
Odpowiedź w 2 minuty:
Poniżej znajdziesz trzy przykłady najpopularniejszych wzorców composables:
// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
// composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const stored = localStorage.getItem(key)
const data = ref(stored ? JSON.parse(stored) : defaultValue)
watch(data, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return data
}
// composables/useFetch.js
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
watchEffect(async () => {
// toValue() obsługuje zarówno ref jak i getter
const urlValue = toValue(url)
loading.value = true
error.value = null
try {
const response = await fetch(urlValue)
data.value = await response.json()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
})
return { data, error, loading }
}
<!-- Użycie composables -->
<script setup>
import { useMouse } from './composables/useMouse'
import { useLocalStorage } from './composables/useLocalStorage'
import { useFetch } from './composables/useFetch'
const { x, y } = useMouse()
const theme = useLocalStorage('theme', 'light')
const { data: users, loading, error } = useFetch('/api/users')
</script>
<template>
<p>Mouse: {{ x }}, {{ y }}</p>
<button @click="theme = theme === 'light' ? 'dark' : 'light'">
Theme: {{ theme }}
</button>
<div v-if="loading">Ładowanie...</div>
<div v-else-if="error">Błąd: {{ error.message }}</div>
<ul v-else>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</template>
Zarządzanie stanem (Vuex/Pinia)
Czym jest Pinia i jak różni się od Vuex?
Odpowiedź w 30 sekund: Pinia to oficjalna biblioteka do zarządzania stanem w Vue 3 (następca Vuex). Różnice: brak mutations (tylko state i actions), pełne wsparcie TypeScript, prostsze API, modułowy design bez namespace. Vuex 4 nadal działa z Vue 3, ale Pinia jest zalecana dla nowych projektów.
Odpowiedź w 2 minuty:
Poniżej znajdziesz kompletny przykład Pinia store wraz z wykorzystaniem w komponencie:
// stores/useUserStore.js - Pinia store
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
// State - reaktywne dane
state: () => ({
user: null,
isAuthenticated: false,
token: null
}),
// Getters - computed properties
getters: {
fullName: (state) => {
return state.user ? `${state.user.firstName} ${state.user.lastName}` : ''
},
isAdmin: (state) => state.user?.role === 'admin'
},
// Actions - metody (sync i async)
actions: {
async login(email, password) {
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
})
const data = await response.json()
this.user = data.user
this.token = data.token
this.isAuthenticated = true
} catch (error) {
throw error
}
},
logout() {
this.user = null
this.token = null
this.isAuthenticated = false
}
}
})
<!-- Użycie Pinia store -->
<script setup>
import { useUserStore } from '@/stores/useUserStore'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
// storeToRefs zachowuje reaktywność
const { user, isAuthenticated, fullName } = storeToRefs(userStore)
// Akcje można destrukturyzować bezpośrednio
const { login, logout } = userStore
const handleLogin = async () => {
await login('user@example.com', 'password')
}
</script>
<template>
<div v-if="isAuthenticated">
<p>Witaj, {{ fullName }}!</p>
<button @click="logout">Wyloguj</button>
</div>
<button v-else @click="handleLogin">Zaloguj</button>
</template>
| Aspekt | Vuex | Pinia |
|---|---|---|
| Mutations | Wymagane | Brak (tylko actions) |
| TypeScript | Słabe wsparcie | Pełne wsparcie |
| Modules | Namespace wymagany | Automatyczne moduły |
| DevTools | Tak | Tak |
| Składnia | Bardziej verbose | Prostsze API |
Vue Router
Jak działa Vue Router i routing w SPA?
Odpowiedź w 30 sekund:
Vue Router mapuje URL-e na komponenty bez przeładowania strony. Definiujesz routes (ścieżka → komponent), używasz <router-link> do nawigacji i <router-view> do renderowania. Obsługuje: dynamiczne segmenty (/users/:id), zagnieżdżone trasy, lazy loading, navigation guards.
Odpowiedź w 2 minuty:
Oto przykład konfiguracji Vue Router z dynamicznymi trasami, lazy loading i navigation guards:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue') // Lazy loading
},
{
path: '/users',
name: 'Users',
component: () => import('@/views/Users.vue'),
// Zagnieżdżone trasy
children: [
{
path: ':id', // Dynamiczny segment
name: 'UserDetail',
component: () => import('@/views/UserDetail.vue'),
props: true // Przekaż params jako props
}
]
},
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
meta: { requiresAuth: true } // Metadata dla guards
},
{
path: '/:pathMatch(.*)*', // Catch-all 404
name: 'NotFound',
component: () => import('@/views/NotFound.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// Global navigation guard
router.beforeEach((to, from, next) => {
const isAuthenticated = checkAuth()
if (to.meta.requiresAuth && !isAuthenticated) {
next({ name: 'Login', query: { redirect: to.fullPath } })
} else {
next()
}
})
export default router
<!-- App.vue -->
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link :to="{ name: 'Users' }">Users</router-link>
<router-link :to="{ name: 'UserDetail', params: { id: 1 } }">
User 1
</router-link>
</nav>
<!-- Tu renderują się komponenty tras -->
<router-view />
</template>
<!-- UserDetail.vue -->
<script setup>
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
// Dostęp do params
const userId = route.params.id
// Programowa nawigacja
const goBack = () => router.back()
const goToUser = (id) => router.push({ name: 'UserDetail', params: { id } })
</script>
Testowanie i best practices
Jak testować komponenty Vue?
Odpowiedź w 30 sekund:
Używaj Vue Test Utils z Jest lub Vitest. mount() renderuje komponent z dziećmi, shallowMount() izoluje komponent. Testuj: renderowanie, interakcje użytkownika (click, input), emitowane eventy, props validation. Mockuj zewnętrzne zależności (store, router, API).
Odpowiedź w 2 minuty:
Poniżej znajdziesz praktyczne przykłady testowania komponentów Vue z wykorzystaniem Vue Test Utils:
// Counter.spec.js
import { mount } from '@vue/test-utils'
import Counter from '@/components/Counter.vue'
describe('Counter', () => {
it('renders initial count', () => {
const wrapper = mount(Counter, {
props: { initialCount: 5 }
})
expect(wrapper.text()).toContain('5')
})
it('increments count on button click', async () => {
const wrapper = mount(Counter)
await wrapper.find('button').trigger('click')
expect(wrapper.text()).toContain('1')
})
it('emits update event', async () => {
const wrapper = mount(Counter)
await wrapper.find('button').trigger('click')
expect(wrapper.emitted('update')).toBeTruthy()
expect(wrapper.emitted('update')[0]).toEqual([1])
})
})
// UserList.spec.js - z mockami
import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import UserList from '@/components/UserList.vue'
describe('UserList', () => {
it('renders users from store', () => {
const wrapper = mount(UserList, {
global: {
plugins: [
createTestingPinia({
initialState: {
user: {
users: [
{ id: 1, name: 'Jan' },
{ id: 2, name: 'Anna' }
]
}
}
})
]
}
})
expect(wrapper.findAll('li')).toHaveLength(2)
expect(wrapper.text()).toContain('Jan')
})
it('calls fetchUsers on mount', () => {
const wrapper = mount(UserList, {
global: {
plugins: [createTestingPinia()]
}
})
const store = useUserStore()
expect(store.fetchUsers).toHaveBeenCalled()
})
})
| Narzędzie | Użycie |
|---|---|
| Vue Test Utils | Oficjalna biblioteka do testów komponentów |
| Vitest | Szybki test runner (rekomendowany dla Vue 3) |
| @pinia/testing | Mockowanie Pinia stores |
| Cypress | Testy E2E |
Zobacz też
- Kompletny Przewodnik - Rozmowa Frontend Developer - pełny przewodnik przygotowania do rozmowy frontend
- Najtrudniejsze Pytania JavaScript - fundamenty JS wymagane w Vue
- Angular vs React Porównanie - porównanie z innymi frameworkami
- Najtrudniejsze Pytania Frameworki JavaScript - Vue w kontekście innych frameworków
Chcesz przećwiczyć te pytania? Sprawdź nasze fiszki Vue.js z 48 pytaniami rekrutacyjnymi - idealne do nauki przed rozmową kwalifikacyjną.
Chcesz więcej pytań rekrutacyjnych?
To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.
