Vue Front-end Инженер

Vue Front-end Инженер

Роадмап навыков для прокачки

Функция setup() и синтаксис script setup

VueJSComponentsComposition API

Основная идея

setup() — это функция компонента, где инициализируется Composition API логика. <script setup> — синтаксический сахар, который компилируется в setup(), но с меньшим количеством boilerplate кода.

Ключевые аспекты

  • setup() — функция, которая выполняется перед созданием компонента, возвращает объект для шаблона
  • <script setup> — компилятор-макрос, всё объявленное автоматически доступно в шаблоне
  • Выполнение — setup вызывается один раз при создании компонента
  • Нет this — в setup нет доступа к this, всё через аргументы props и context
  • Рекомендуется<script setup> является рекомендуемым подходом в Vue 3

Плюсы script setup

  • Меньше boilerplate — не нужен return
  • Компоненты автоматически регистрируются
  • Лучшая производительность — компилируется в более эффективный код
  • Проще синтаксис — меньше вложенности

Минусы script setup

  • Нельзя использовать некоторые опции (name, inheritAttrs) без отдельного script блока
  • Макросы (defineProps, defineEmits) работают только в <script setup>
  • Сложнее мигрировать старый код

Частые ошибки на собеседованиях

  • Не знают разницу между setup() и <script setup>
  • Пытаются использовать this в setup
  • Забывают про return в обычном setup() (не в script setup)
  • Не знают про defineProps и defineEmits в script setup

Введение и проблематика

В Vue 3 Composition API логика компонента описывается в функции setup(). Но писать setup() вручную требует boilerplate: нужно возвращать всё для шаблона, регистрировать компоненты.

<script setup> решает эту проблему — это компилятор-макрос, который автоматически делает всё объявленное доступным в шаблоне.

script setup — рекомендуемый способ написания компонентов во Vue 3. Он компилируется в более эффективный код и требует меньше boilerplate.


Базовая теория

setup() функция

Code Example 1: Чем отличаются эти два подхода? Какие преимущества даёт <script setup>?

vue
<template>
  <p>{{ message }}</p>
  <button @click="greet">Привет</button>
</template>
 
<script>
import { ref } from 'vue'
 
export default {
  setup() {
    const message = ref('Привет, Vue!')
 
    function greet() {
      alert(message.value)
    }
 
    // Нужно вернуть всё, что используется в шаблоне
    return {
      message,
      greet
    }
  }
}
</script>

<script setup>

vue
<template>
  <p>{{ message }}</p>
  <button @click="greet">Привет</button>
</template>
 
<script setup>
import { ref } from 'vue'
 
const message = ref('Привет, Vue!')
 
function greet() {
  alert(message.value)
}
 
// Не нужен return — всё автоматически доступно
</script>

Практические примеры

Регистрация компонентов

Code Example 2: Как отличается регистрация компонентов в <script setup> и в setup()? Что нужно сделать дополнительно во втором варианте?

vue
<template>
  <MyButton @click="handleClick">
    Нажми
  </MyButton>
</template>
 
<script setup>
// Компонент автоматически доступен в шаблоне
import MyButton from './MyButton.vue'
 
function handleClick() {
  console.log('Клик!')
}
</script>

Работа с props

Code Example 3: Как объявляются props в <script setup>? Нужно ли импортировать defineProps?

vue
<template>
  <p>{{ title }} - {{ count }}</p>
</template>
 
<script setup>
// defineProps — компилятор-макрос, не нужен импорт
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  }
})
 
// Доступ к props
console.log(props.title)
</script>

Эмиты событий

Code Example 4: Как объявляются emit-события в <script setup>? Как вызвать событие?

vue
<template>
  <button @click="handleClick">Отправить</button>
</template>
 
<script setup>
// defineEmits — компилятор-макрос
const emit = defineEmits(['submit', 'cancel'])
 
function handleClick() {
  emit('submit', { data: 'payload' })
}
</script>

TypeScript с props и emits

Code Example 5: Как типизировать props и emits в <script setup> с TypeScript? Что делает withDefaults?

vue
<script setup lang="ts">
interface Props {
  title: string
  count?: number
}
 
const props = withDefaults(defineProps<Props>(), {
  count: 0
})
 
const emit = defineEmits<{
  (e: 'submit', value: string): void
  (e: 'cancel'): void
}>()
</script>

Аргументы setup()

При использовании обычного setup() есть доступ к двум аргументам:

Code Example 6: Какие аргументы принимает setup() и какие макросы заменяют их в <script setup>?

vue
<script>
export default {
  props: {
    title: String
  },
  emits: ['update'],
  setup(props, context) {
    // props — реактивный объект с пропсами
    console.log(props.title)
 
    // context содержит:
    // - attrs — не-prop атрибуты
    // - slots — слоты
    // - emit — функция для эмита событий
    // - expose — функция для expose
 
    const { attrs, slots, emit, expose } = context
 
    emit('update', 'новое значение')
 
    return {}
  }
}
</script>

В <script setup> эти же возможности доступны через макросы:

vue
<script setup>
import { useAttrs, useSlots } from 'vue'
 
const props = defineProps(['title'])
const emit = defineEmits(['update'])
const attrs = useAttrs()
const slots = useSlots()
</script>

Пограничные кейсы

⚠️

Для некоторых опций компонента (name, inheritAttrs) нужен дополнительный обычный script блок.

Code Example 8: Зачем может понадобиться обычный <script> рядом с <script setup>? Какие опции нельзя указать в <script setup>?

vue
<script>
export default {
  name: 'MyCustomComponent',
  inheritAttrs: false
}
</script>
 
<script setup>
import { ref } from 'vue'
 
const count = ref(0)
</script>
🚫

defineProps и defineEmits — это компилятор-макросы. Их нельзя импортировать и использовать вне script setup.

Code Example 9: Почему нельзя импортировать defineProps и defineEmits? Что это за конструкции?

vue
<script setup>
// ✅ Правильно — макросы используются напрямую
const props = defineProps(['title'])
const emit = defineEmits(['click'])
 
// ❌ Неправильно — нельзя импортировать
// import { defineProps } from 'vue'
</script>

defineExpose

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

Code Example 7: Зачем нужен defineExpose? Что произойдёт, если не вызвать его в <script setup>?

vue
<script setup>
import { ref } from 'vue'
 
const count = ref(0)
const publicMethod = () => console.log('Публичный метод')
const privateMethod = () => console.log('Приватный метод')
 
// Только эти методы будут доступны через ref
defineExpose({
  count,
  publicMethod
})
</script>

Использование в родителе:

vue
<template>
  <ChildComponent ref="childRef" />
</template>
 
<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'
 
const childRef = ref(null)
 
onMounted(() => {
  console.log(childRef.value.count) // Работает
  childRef.value.publicMethod()     // Работает
  // childRef.value.privateMethod() // Ошибка — не expose
})
</script>

Плюсы и минусы

Аспектsetup()<script setup>
BoilerplateБольшеМеньше
ПроизводительностьСтандартнаяЛучше (оптимизации компилятора)
Регистрация компонентовРучнаяАвтоматическая
TypeScriptХорошоОтлично
ГибкостьВышеОграниченная
Опции компонентаВсе доступныНужен доп. script

Вопросы интервьюера

Q: Чем <script setup> отличается от setup()?

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

Q: Как получить доступ к props в script setup?

Через макрос defineProps: const props = defineProps(['title']).

Q: Как эмитить события в script setup?

Через макрос defineEmits: const emit = defineEmits(['click']), затем emit('click', data).

Q: Можно ли использовать name и inheritAttrs в script setup?

Да, но нужен дополнительный обычный script блок с export default.


Источники

Code Example 1: setup() функция vs script setup

❓ Чем отличаются эти два подхода? Какие преимущества даёт <script setup>?

setup():

vue
<template>
  <p>{{ message }}</p>
  <button @click="greet">Привет</button>
</template>
 
<script>
import { ref } from 'vue'
 
export default {
  setup() {
    const message = ref('Привет, Vue!')
 
    function greet() {
      alert(message.value)
    }
 
    return {
      message,
      greet
    }
  }
}
</script>

script setup:

vue
<template>
  <p>{{ message }}</p>
  <button @click="greet">Привет</button>
</template>
 
<script setup>
import { ref } from 'vue'
 
const message = ref('Привет, Vue!')
 
function greet() {
  alert(message.value)
}
</script>

Code Example 2: Регистрация компонентов

❓ Как отличается регистрация компонентов в <script setup> и в setup()? Что нужно сделать дополнительно во втором варианте?

Вариант A — script setup:

vue
<template>
  <MyButton @click="handleClick">
    Нажми
  </MyButton>
</template>
 
<script setup>
import MyButton from './MyButton.vue'
 
function handleClick() {
  console.log('Клик!')
}
</script>

Вариант B — setup():

vue
<template>
  <MyButton @click="handleClick">
    Нажми
  </MyButton>
</template>
 
<script>
import MyButton from './MyButton.vue'
 
export default {
  components: {
    MyButton
  },
  setup() {
    function handleClick() {
      console.log('Клик!')
    }
 
    return { handleClick }
  }
}
</script>

Code Example 3: Работа с props

❓ Как объявляются props в <script setup>? Нужно ли импортировать defineProps?

vue
<template>
  <p>{{ title }} - {{ count }}</p>
</template>
 
<script setup>
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  }
})
 
console.log(props.title)
</script>

Code Example 4: Эмиты событий

❓ Как объявляются emit-события в <script setup>? Как вызвать событие?

vue
<template>
  <button @click="handleClick">Отправить</button>
</template>
 
<script setup>
const emit = defineEmits(['submit', 'cancel'])
 
function handleClick() {
  emit('submit', { data: 'payload' })
}
</script>

Code Example 5: TypeScript с props и emits

❓ Как типизировать props и emits в <script setup> с TypeScript? Что делает withDefaults?

vue
<script setup lang="ts">
interface Props {
  title: string
  count?: number
}
 
const props = withDefaults(defineProps<Props>(), {
  count: 0
})
 
const emit = defineEmits<{
  (e: 'submit', value: string): void
  (e: 'cancel'): void
}>()
</script>

Code Example 6: Аргументы setup() vs макросы script setup

❓ Какие аргументы принимает setup() и какие макросы заменяют их в <script setup>?

setup():

vue
<script>
export default {
  props: {
    title: String
  },
  emits: ['update'],
  setup(props, context) {
    console.log(props.title)
 
    const { attrs, slots, emit, expose } = context
 
    emit('update', 'новое значение')
 
    return {}
  }
}
</script>

script setup:

vue
<script setup>
import { useAttrs, useSlots } from 'vue'
 
const props = defineProps(['title'])
const emit = defineEmits(['update'])
const attrs = useAttrs()
const slots = useSlots()
</script>

Code Example 7: defineExpose

❓ Зачем нужен defineExpose? Что произойдёт, если не вызвать его в <script setup>?

Дочерний компонент:

vue
<script setup>
import { ref } from 'vue'
 
const count = ref(0)
const publicMethod = () => console.log('Публичный метод')
const privateMethod = () => console.log('Приватный метод')
 
defineExpose({
  count,
  publicMethod
})
</script>

Родительский компонент:

vue
<template>
  <ChildComponent ref="childRef" />
</template>
 
<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'
 
const childRef = ref(null)
 
onMounted(() => {
  console.log(childRef.value.count)
  childRef.value.publicMethod()
})
</script>

Code Example 8: Двойной script блок

❓ Зачем может понадобиться обычный <script> рядом с <script setup>? Какие опции нельзя указать в <script setup>?

vue
<script>
export default {
  name: 'MyCustomComponent',
  inheritAttrs: false
}
</script>
 
<script setup>
import { ref } from 'vue'
 
const count = ref(0)
</script>

Code Example 9: Ошибки при использовании макросов

❓ Почему нельзя импортировать defineProps и defineEmits? Что это за конструкции?

vue
<script setup>
const props = defineProps(['title'])
const emit = defineEmits(['click'])
</script>