Vue.js - Pytania Rekrutacyjne dla Frontend Developera [2026]

Sławomir Plamowski 18 min czytania
composition-api frontend javascript pinia pytania-rekrutacyjne vue vuejs vuex

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

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">
// Composition API z <script setup> - najprostsza składnia Vue 3
import { ref, computed } from 'vue'

interface Props {
  name: string
  email: string
  avatar?: string
}

const props = defineProps<Props>()
const emit = defineEmits<{
  (e: 'select', id: number): void
}>()

const isExpanded = ref(false)
const initials = computed(() =>
  props.name.split(' ').map(n => n[0]).join('')
)

const toggleExpand = () => {
  isExpanded.value = !isExpanded.value
}
</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 return ani export 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 }}</li>
  </ul>

  <p>Suma: {{ totalPrice }}</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ż


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.

Kup pełny dostęp Zobacz bezpłatny podgląd
Powrót do blogu

Zostaw komentarz

Pamiętaj, że komentarze muszą zostać zatwierdzone przed ich opublikowaniem.