Vue.jsMiddleTechnical

Что такое <script setup> и каковы его преимущества перед стандартным Composition API setup?

<script setup> — синтаксический сахар над setup(), где всё объявленное автоматически доступно в шаблоне без явного return. Он убирает шаблонный код, улучшает вывод типов и чуть ускоряет компиляцию.

Что такое `<script setup>`

<script setup> — это специальный атрибут блока <script> в однофайловых компонентах Vue 3 (SFC). Во время компиляции содержимое блока превращается в функцию setup(): все переменные, функции и импорты на верхнем уровне автоматически становятся доступны в шаблоне без явного return { ... }.

Сравнение с обычным Composition API

Обычный способ с явным возвратом:

<script>
import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const double = computed(() => count.value * 2)
    function increment() { count.value++ }
    return { count, double, increment }  // обязательно!
  }
}
</script>

С <script setup>:

<script setup>
import { ref, computed } from 'vue'

const count = ref(0)
const double = computed(() => count.value * 2)
function increment() { count.value++ }
// return не нужен
</script>

<template>
  <button @click="increment">{{ count }} (x2 = {{ double }})</button>
</template>

Преимущества

  • Меньше шаблонного кода — не нужен export default { setup() { return {} } }.
  • Лучший вывод типов TypeScript — компилятор видит переменные напрямую, IDE подсказывает типы в шаблоне.
  • Производительность компилятора — Vite/Vue-компилятор может делать более точный tree-shaking.
  • Автоматическая регистрация компонентов — импортированный компонент сразу доступен в шаблоне.
  • Более чистый scoping — нет случайной утечки переменных через неверный return.

defineProps и defineEmits

Макросы defineProps и defineEmits работают только внутри <script setup> и не требуют импорта:

<script setup lang="ts">
const props = defineProps<{
  title: string
  count?: number
}>()

const emit = defineEmits<{
  (e: 'update', value: number): void
}>()

function send() {
  emit('update', (props.count ?? 0) + 1)
}
</script>

defineExpose

В отличие от обычного setup(), компоненты с <script setup> по умолчанию закрыты для родителей. Чтобы открыть методы через ref, используйте defineExpose:

<script setup>
import { ref } from 'vue'
const inputRef = ref(null)
function focus() { inputRef.value?.focus() }
defineExpose({ focus })
</script>

Подводные камни

  • Макросы (defineProps, defineEmits, defineExpose, withDefaults) — только внутри <script setup>; вызов снаружи вызовет ошибку компилятора.
  • Нельзя смешивать <script setup> и Options API в одном компоненте (но можно иметь два <script>-блока: один с setup, один без — для inheritAttrs: false и т.п.).
  • Динамические компоненты через переменную (:is="MyComp") работают только если переменная объявлена на верхнем уровне блока.
  • await на верхнем уровне (top-level await) поддерживается, но оборачивает компонент в Suspense — убедитесь, что родитель его предоставляет.
  • Старые инструменты (Vetur, Jest без @vue/vue3-jest) не понимают <script setup> — нужен свежий Volar и соответствующий трансформер.
  • Тип пропсов, объявленных через generic-синтаксис (defineProps<{}>()), не может использовать импортированные типы из других модулей без флага compilerOptions.propsDestructureTransform в Vue < 3.3.
  • Reactive destructuring пропсов потеряет реактивность без toRefs или Vue 3.3+ defineProps destructure.

Common mistakes

  • Возвращать что-то вручную (return { x }) — это лишний код, компилятор всё сделал.
  • Импортировать defineProps явно.
  • Пытаться использовать this.
  • Обращаться к локальным переменным извне без defineExpose.

What the interviewer is testing

  • Может показать минимальный <script setup>-компонент.
  • Знает про автоматический импорт компонентов в шаблон.
  • Понимает связь макросов и SFC-компилятора.
  • Видит ограничение «только в SFC».

Sources

Related topics