Vue.js Script Setup
<script setup> is a compile-time syntactic sugar for using the Composition API inside Single File Components (SFCs). It provides a more concise way to write Vue components with better performance and TypeScript support.
Available Functions
Component Communication
defineProps()- Define component props with type checking and validationdefineEmits()- Define component events for parent-child communication
Additional Script Setup Functions
defineExpose()- Expose component methods/properties to parent componentsuseSlots()- Access component slots programmaticallyuseAttrs()- Access component attributes (fallthrough attributes)
Basic Syntax
Traditional vs Script Setup
Traditional Composition API:
<script>
import { ref, computed } from 'vue'
export default {
props: ['title'],
emits: ['update'],
setup(props, { emit }) {
const count = ref(0)
const increment = () => {
count.value++
emit('update', count.value)
}
return {
count,
increment
}
}
}
</script>
Script Setup (Modern):
<script setup>
import { ref } from 'vue'
const props = defineProps(['title'])
const emit = defineEmits(['update'])
const count = ref(0)
const increment = () => {
count.value++
emit('update', count.value)
}
</script>
Key Benefits
1. Less Boilerplate
- No need for
export default - No need to return values from setup
- Automatic exposure of variables to template
2. Better Performance
- Variables are compiled to be directly accessible
- Reduced component instance creation overhead
3. Better TypeScript Support
- Improved type inference
- Better IDE support and autocomplete
4. Cleaner Code Organization
<script setup>
// 1. Imports
import { ref, computed, onMounted } from 'vue'
import MyComponent from './MyComponent.vue'
// 2. Props and emits
const props = defineProps({
title: String,
count: { type: Number, default: 0 }
})
const emit = defineEmits(['update', 'delete'])
// 3. Reactive data
const localCount = ref(props.count)
const message = ref('')
// 4. Computed properties
const displayTitle = computed(() =>
props.title ? props.title.toUpperCase() : 'Default Title'
)
// 5. Methods
const handleClick = () => {
localCount.value++
emit('update', localCount.value)
}
// 6. Lifecycle hooks
onMounted(() => {
console.log('Component mounted')
})
</script>
Migration from Options API
Before (Options API)
<script>
export default {
props: ['title'],
data() {
return {
count: 0
}
},
computed: {
doubleCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log('mounted')
}
}
</script>
After (Script Setup)
<script setup>
const props = defineProps(['title'])
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
onMounted(() => {
console.log('mounted')
})
</script>
Best Practices
- Organize code logically: imports → props/emits → reactive data → computed → methods → lifecycle
- Use TypeScript: Better type safety and IDE support
- Keep components focused: Use composables for reusable logic
- Validate props: Always define prop types and validation
- Use meaningful event names: Make component communication clear
Limitations
- Cannot use
awaitat the top level (use<Suspense>instead) - Cannot conditionally call
definePropsordefineEmits - Limited support for dynamic component registration