Vue 3 Script Setup i defineModel w 2026: Nowoczesna Składnia i Pytania Rekrutacyjne
Kompleksowy przewodnik po składni script setup w Vue 3 oraz makrze defineModel. Omówienie reactive props destructure, typowania TypeScript oraz pytań rekrutacyjnych.

Składnia script setup w Vue 3 rewolucjonizuje sposób tworzenia komponentów, eliminując zbędny kod szablonowy i znacząco poprawiając integrację z TypeScript. W połączeniu z defineModel, wprowadzonym eksperymentalnie w Vue 3.3 i ustabilizowanym w Vue 3.4, budowanie komponentów z dwukierunkowym wiązaniem danych wymaga znacznie mniej kodu niż ekwiwalent w Options API.
Od Vue 3.5 właściwości (props) można destrukturyzować bezpośrednio z defineProps zachowując reaktywność. W większości przypadków eliminuje to potrzebę używania toRefs.
Podstawy Script Setup w Vue 3.5
Składnia <script setup> to cukier składniowy czasu kompilacji dla Composition API wewnątrz komponentów Single File. Zmienne zadeklarowane na najwyższym poziomie są automatycznie eksponowane do szablonu, a importy można używać bezpośrednio bez rejestracji.
<!-- UserCard.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { User } from '@/types'
// Props z interfejsem TypeScript
const props = defineProps<{
user: User
showEmail?: boolean
}>()
// Stan reaktywny
const isExpanded = ref(false)
// Właściwość computed
const displayName = computed(() => {
return `${props.user.firstName} ${props.user.lastName}`
})
// Emiter zdarzeń z typowanym payload
const emit = defineEmits<{
select: [userId: string]
delete: [userId: string]
}>()
// Metody to zwykłe funkcje
function handleSelect() {
emit('select', props.user.id)
}
</script>
<template>
<div class="user-card" @click="handleSelect">
<h3>{{ displayName }}</h3>
<p v-if="showEmail">{{ user.email }}</p>
<button @click.stop="isExpanded = !isExpanded">
{{ isExpanded ? 'Zwiń' : 'Rozwiń' }}
</button>
</div>
</template>Kompilator automatycznie obsługuje instrukcje eksportu i rejestrację komponentów. Redukuje to obciążenie poznawcze w porównaniu do jawnego zwracania wartości z funkcji setup().
Destrukturyzacja Reaktywnych Props (Vue 3.5+)
Przed Vue 3.5 destrukturyzacja props przerywała reaktywność, ponieważ destrukturyzacja w JavaScript tworzy migawkę wartości w danym momencie. Vue 3.5 wprowadza śledzenie na poziomie kompilatora, które zachowuje reaktywność dla destrukturyzowanych props.
<!-- SearchInput.vue -->
<script setup lang="ts">
import { watch } from 'vue'
// Destrukturyzacja z domyślnymi wartościami - pozostaje reaktywna w 3.5+
const { query = '', placeholder = 'Szukaj...' } = defineProps<{
query?: string
placeholder?: string
}>()
// Watch działa na destrukturyzowanych props
watch(
() => query,
(newQuery) => {
console.log('Zapytanie się zmieniło:', newQuery)
}
)
</script>
<template>
<input
:value="query"
:placeholder="placeholder"
@input="$emit('update:query', ($event.target as HTMLInputElement).value)"
/>
</template>Ta składnia wymaga Vue 3.5 lub nowszego. Dokumentacja Vue 3.5 release announcement opisuje pełne zachowanie destrukturyzacji reaktywnych props.
defineModel dla Dwukierunkowego Wiązania
Przed defineModel implementacja v-model na własnym komponencie wymagała deklaracji prop i ręcznego emitowania zdarzenia aktualizacji. Ten wzorzec występuje w niezliczonych bazach kodu Vue:
<!-- Stary sposób - rozwlekły ale jawny -->
<script setup lang="ts">
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
function updateValue(event: Event) {
emit('update:modelValue', (event.target as HTMLInputElement).value)
}
</script>Z defineModel ten sam komponent staje się znacznie krótszy:
<!-- TextInput.vue -->
<script setup lang="ts">
// defineModel tworzy ref synchronizowany z v-model
const model = defineModel<string>({ default: '' })
</script>
<template>
<input
v-model="model"
type="text"
class="text-input"
/>
</template>Komponent nadrzędny używa standardowej składni v-model:
<!-- ParentForm.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import TextInput from './TextInput.vue'
const username = ref('')
</script>
<template>
<TextInput v-model="username" />
<p>Nazwa użytkownika: {{ username }}</p>
</template>defineModel zwraca ref, który pozostaje zsynchronizowany z wartością powiązaną przez rodzica. Mutacje tego ref automatycznie emitują zdarzenie aktualizacji.
Gotowy na rozmowy o Vue.js / Nuxt.js?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Nazwane Modele i Wiele Wiązań v-model
Komponenty czasami potrzebują wielu dwukierunkowych wiązań. Vue obsługuje nazwane wiązania v-model, a defineModel obsługuje je poprzez przyjmowanie argumentu nazwy.
<!-- DateRangePicker.vue -->
<script setup lang="ts">
// Nazwane modele dla wielu wiązań v-model
const startDate = defineModel<Date>('start', { required: true })
const endDate = defineModel<Date>('end', { required: true })
// Walidacja computed
const isValidRange = computed(() => {
if (!startDate.value || !endDate.value) return true
return startDate.value <= endDate.value
})
</script>
<template>
<div class="date-range-picker">
<input
type="date"
:value="startDate?.toISOString().split('T')[0]"
@input="startDate = new Date(($event.target as HTMLInputElement).value)"
/>
<span>do</span>
<input
type="date"
:value="endDate?.toISOString().split('T')[0]"
@input="endDate = new Date(($event.target as HTMLInputElement).value)"
/>
<p v-if="!isValidRange" class="error">Data końcowa musi być późniejsza niż początkowa</p>
</div>
</template>Komponent nadrzędny wiąże oba modele:
<DateRangePicker v-model:start="filterStart" v-model:end="filterEnd" />Modyfikatory v-model z defineModel
Wbudowane modyfikatory Vue jak .trim, .number i .lazy działają automatycznie z defineModel. Niestandardowe modyfikatory wymagają jawnej obsługi przez drugi element zwracanej krotki.
<!-- CurrencyInput.vue -->
<script setup lang="ts">
// Dostęp do modyfikatorów przez drugą zwracaną wartość
const [model, modifiers] = defineModel<number>({
default: 0,
// Funkcje transformacji dla niestandardowych modyfikatorów
set(value) {
// Zaokrąglij do 2 miejsc po przecinku jeśli modyfikator 'round' jest obecny
if (modifiers.round) {
return Math.round(value * 100) / 100
}
return value
}
})
</script>
<template>
<div class="currency-input">
<span>zł</span>
<input
type="number"
step="0.01"
:value="model"
@input="model = parseFloat(($event.target as HTMLInputElement).value) || 0"
/>
</div>
</template>Użycie z niestandardowym modyfikatorem:
<CurrencyInput v-model.round="price" />Wzorce TypeScript dla Script Setup
Bezpieczeństwo typów w komponentach <script setup> opiera się na generykach przekazywanych do makr kompilatora. Wzorzec różni się nieznacznie między props, emits i modelami.
<!-- GenericList.vue -->
<script setup lang="ts" generic="T extends { id: string }">
import { computed } from 'vue'
// Props generycznego komponentu
const props = defineProps<{
items: T[]
selected?: T
}>()
const emit = defineEmits<{
select: [item: T]
remove: [id: string]
}>()
// Sloty też mogą być typowane
defineSlots<{
default: (props: { item: T; index: number }) => any
empty: () => any
}>()
const hasItems = computed(() => props.items.length > 0)
</script>
<template>
<ul v-if="hasItems">
<li
v-for="(item, index) in items"
:key="item.id"
:class="{ selected: selected?.id === item.id }"
@click="emit('select', item)"
>
<slot :item="item" :index="index">
{{ item.id }}
</slot>
</li>
</ul>
<div v-else>
<slot name="empty">Brak elementów</slot>
</div>
</template>Komponenty generyczne, dostępne od Vue 3.3, umożliwiają tworzenie wielokrotnego użytku typowanych komponentów bez poświęcania wnioskowania typów w kodzie konsumującym. Dla głębszych wzorców dokumentacja Vue o TypeScript omawia przypadki brzegowe.
Typowe Pytania Rekrutacyjne o Script Setup
Rekruterzy testujący wiedzę o Vue często koncentrują się na różnicach między script setup a Options API, jak również na mechanice reaktywności.
Pytanie: Dlaczego destrukturyzacja props przerywa reaktywność i jak Vue 3.5 to rozwiązuje?
W JavaScript destrukturyzacja tworzy nowe zmienne przechowujące wartości w momencie destrukturyzacji. Jeśli props.count zmieni się później, destrukturyzowana const { count } = props nadal przechowuje starą wartość. Kompilator Vue 3.5 przekształca destrukturyzowane zmienne w gettery, które ponownie ewaluują przy dostępie, zachowując reaktywność bez narzutu runtime.
Pytanie: Co dzieje się pod spodem gdy używane jest defineModel?
Kompilator rozwija defineModel w deklarację prop i rejestrację emit. W runtime tworzy ref, którego getter odczytuje wartość prop, a setter wywołuje emit('update:modelValue', newValue). Ten ref nie jest prawdziwą kopią prop, ale proxy, które odpowiednio przekierowuje odczyty i zapisy.
Pytanie: Kiedy komponent powinien używać script setup zamiast zwykłej funkcji setup?
Script setup jest preferowany dla większości komponentów single-file, ponieważ redukuje kod szablonowy i poprawia wsparcie IDE. Zwykła funkcja setup() jest niezbędna gdy komponent musi eksponować metody do komponentów nadrzędnych przez defineExpose w złożonych wzorcach, lub gdy budowane są komponenty renderless zwracające funkcje render bezpośrednio. Do przygotowania do rozmowy rekrutacyjnej warto poznać więcej wzorców Vue composables.
Migracja z Options API do Script Setup
Istniejące bazy kodu często zawierają komponenty Options API, które mogą skorzystać z migracji. Konwersja podąża za przewidywalnym wzorcem:
<!-- Przed: Options API -->
<script lang="ts">
import { defineComponent, PropType } from 'vue'
export default defineComponent({
props: {
items: { type: Array as PropType<string[]>, required: true }
},
emits: ['select'],
data() {
return { searchTerm: '' }
},
computed: {
filteredItems() {
return this.items.filter(item =>
item.toLowerCase().includes(this.searchTerm.toLowerCase())
)
}
},
methods: {
selectItem(item: string) {
this.$emit('select', item)
}
}
})
</script><!-- Po: Script Setup -->
<script setup lang="ts">
import { ref, computed } from 'vue'
const props = defineProps<{ items: string[] }>()
const emit = defineEmits<{ select: [item: string] }>()
const searchTerm = ref('')
const filteredItems = computed(() =>
props.items.filter(item =>
item.toLowerCase().includes(searchTerm.value.toLowerCase())
)
)
function selectItem(item: string) {
emit('select', item)
}
</script>Zmigrowana wersja jest krótsza i wnioskowanie typów działa bez rzutowań PropType. Zespół Vue dostarcza vue-codemod do automatycznych migracji, choć ręczny przegląd pozostaje konieczny dla złożonych komponentów.
Rozważania Wydajnościowe w Script Setup
Komponenty script setup kompilują się do bardziej wydajnego kodu, ponieważ kompilator zna dokładny kształt komponentu w czasie budowania. Konkretnie:
- Wiązania szablonu rozwiązywane są w czasie kompilacji zamiast runtime property lookup
- Nieużywane importy są efektywniej usuwane przez tree-shaking
- Brak opakowującej funkcji redukuje narzut closure
Dla aplikacji priorytetyzujących rozmiar bundle, script setup z Vapor Mode (dostępnym w Vue 3.6+) eliminuje wirtualny DOM całkowicie dla opted-in komponentów, generując bezpośrednie operacje DOM.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Kluczowe Wnioski dla Vue 3 Script Setup i defineModel
- Script setup redukuje kod szablonowy komponentu automatycznie eksponując wiązania najwyższego poziomu do szablonów i obsługując rejestrację komponentów
defineModelzastępuje ręczny wzorzec prop + emit dlav-model, zwracając ref synchronizowany z wartością powiązaną przez rodzica- Vue 3.5 wprowadza destrukturyzację reaktywnych props, pozwalając na
const { prop } = defineProps()bez utraty reaktywności - Nazwane modele obsługują wiele wiązań
v-modelna pojedynczym komponencie używającdefineModel('name') - Generyki TypeScript działają z
defineProps,defineEmits,defineSlotsi generycznymi komponentami przez atrybutgeneric - Migracja z Options API podąża za mechanicznym wzorcem: data staje się refs, computed pozostaje computed, metody stają się funkcjami, a kompilator obsługuje eksporty
Znajdziesz błąd w Vue.js / Nuxt.js?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 18 września 2026
Tagi
Udostępnij
Powiązane artykuły

Zaawansowane Vue 3 Composables: Wzorce wielokrotnego uzytku i pytania rekrutacyjne 2026
Kompleksowy przewodnik po zaawansowanych Vue 3 Composables z wzorcami wielokrotnego uzytku, asynchroniczna obsluga bledow, Dependency Injection, walidacja formularzy i aktualne pytania rekrutacyjne na 2026 rok.

Nuxt 4 w 2026: Nowa Struktura Katalogow i Migracja z Nuxt 3
Kompletny przewodnik po Nuxt 4: nowa struktura katalogu app/, migracja krok po kroku z Nuxt 3, singleton data fetching, shallow reactivity, TypeScript context splitting, Vue Router v5, zarzadzanie meta tagami i lista kontrolna migracji.

Vue 3 Pinia vs Vuex: Nowoczesne zarządzanie stanem i pytania rekrutacyjne 2026
Porównanie Pinia i Vuex: architektura, TypeScript, Composition API, migracja, hydratacja SSR oraz najczęstsze pytania rekrutacyjne o zarządzanie stanem Vue na rok 2026.