Framework/Vue

Vue3 <script setup> 정리: defineProps, defineEmits, defineExpose

깨구르르 2025. 11. 26. 11:51
728x90
반응형

1. 서론

Vue 3 공식 문서와 예제들을 보다 보면

<script>
export default {
	// ...
}
</script>

위와 같은 코드보다 아래와 같은 코드가 더 자주 보입니다.

<script setup>
// ...
</script>

Vue에서는 <script setup>SFC(Single File Component)에서 가장 권장되는 방식이라고 명시하고 있습니다.

이는 Composition API를 쓸 때 보일러플리에트를 줄이고, 타입 지원과 최적화를 받기 위해 도입된 문법입니다.

 

정리하자면 <script setup>은 Composition API를 더 짧고 직관적으로 쓰게 해주고,

export default, return과 같은 반복 코드를 없애주고, 빌드 타임에 최적화까지 되는 문법이에요.

 

이번 글에서는 기존 setup() 방식과 <script setup>을 비교하고

pros/emit을 어떻게 정의하는지, 템플릿과 어떻게 연결되는지까지 한 번에 정리해보겠습니다.

 


 

2. 기존 setup()과 <script setup> 비교

먼저 Composition API를 기존 setup() 버전으로 썼을 때 코드를 보겠습니다.

일반 setup() 버전

<!-- Counter.vue -->
<template>
	<div>
    	<p>Count: {{ count }}</p>
        <button @click="increment">+1</button>
    </div>
</template>

<script>
import { ref } from 'vue'

export default {
	setup() {
    	const count = ref(0)
        
        const increment = () => {
        	count.value++
        }
        
        return {
        	count,
            increment
        }
    }
}
</script>
  • export default { setup() { ... } } 보일러플레이트
  • 템플릿에서 쓰려면 return { count, increment } 해줘야 함

 


 

<script setup> 버전

같은 기능을 <script setup>으로 바꾸면 이렇게 됩니다.

<!-- Counter.vue -->
<template>
	<div>
    	<p>Count: {{ count }}</p>
        <button @click="increment">+1</button>
    </div>
</template>

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

const count = ref(0)

const increment = () => {
    count.value++
}
</script>
  • export default 없음
  • setup() 함수 선언도 없음
  • return도 없음

 

<script setup> 안에서 선언한 변수/함수는 자동으로 템플릿에서 접근 가능합니다.

Vue 컴파일러가 빌드 타임에 이 코드를 setup()으로 변환해주는 구조라서 가능한 문법이에요.

 


 

3. <script setup>의 핵심 규칙 3가지

첫 번째, 컴파일 타임 전용 문법

런타임에 <script setup>이라는 개념은 존재하지 않고,

빌드할 때 자동으로 setup() 함수로 변합니다.

 

두 번째, 템플릿에서 자동으로 노출

<script setup>에서 선언된 최상위 변수와 함수는

return 없이도 템플릿에서 사용할 수 있습니다.

 

세 번째, 컴포넌트 옵션 대신 전용 매크로 사용

defineProps, defineEmits, defineExpose 같은 컴파일러 매크로를 사용해

props/emit/노출 API 등을 선언합니다.

 


 

4. props와 emits: defineProps, defineEmits

기존 props / emits 방식

<script>
export default {
	props: {
    	msg: {
        	type: String,
            required: true
        }
    },
    
    emits: ['update'],
    
    setup(props, { emit }) {
    	function doUpdate() {
        	emit('update', '새 값')
        }
        
        return { doUpdate }
    }
}
</script>

 


 

<script setup> 버전 - defineProps, defineEmits

<script setup>
const props = defineProps({
	msg: {
    	type: String,
        required: true
    }
})

const emit = defineEmits(['update'])

function doUpdate() {
	emit('update', '새 값)
}
</script>

<template>
	<div>
    	<p>{{ props.msg }}</p>
        <button @click="doUpdate">업데이트</button>
    </div>
</template>
  • defineProps, defineEmits는 전역 함수처럼 보이지만, 실제로는 컴파일러가 처리하는 매크로입니다.
  • import 할 필요 없고, <script setup> 안에서만 사용할 수 있습니다.

타입스크립트를 쓸 경우에는 제네릭을 얹어서 더 강하게 타입 체크를 할 수 있는데,

JS만 쓸 때는 위처럼 객체 옵션으로 선언하면 됩니다.

 


 

5. defineExpose: 부모에서 자식의 메서드/변수 노출

가끔씩 부모 컴포넌트에서 자식 컴포넌트의 메서드에 직접 접근해야 할 때가 있습니다.

Vue 3에서는 <script setup>에서 defineExpose를 사용해 노출할 수 있습니다.

<!-- ChildInput.vue -->
<template>
	<input ref="inputRef" v-model="value" />
</template>

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

const value = ref('')
const inputRef = ref(null)

function focus() {
	inputRef.value?.focus()
}

defineExpose({
	focus
})
</script>
<!-- Parent.vue -->
<template>
	<ChildInput ref="child" />
    <button @click="focusChild">자식 인풋 포커스</button>
</template>

<script setup>
import { ref } from 'vue'
import ChildInput from './ChildInput.vue'

const child = ref(null)

function focusChild() {
	child.value?.focus()
}
</script>
  • defineExpose({ focus })로 노출한 메서드는 부모에서 ref로 잡았을 때, focus()로 접근 가능합니다.

 


 

6. <script setup>과 <script>를 동시에 쓸 수 있을까?

기존 Options API와 <script setup>을 한 컴포넌트에서 같이 쓰고 싶은 사람도 있을 것입니다.

Vue 3에서는 하나의 SFC 안에 <script>와 <script setup>을 동시에 둘 수 있지만,

일반적으로는 <script setup> 하나만 쓰는 것을 권장합니다.

 

공식 문서에 따르면 아래와 같습니다.

  • <script>는 여전히 export default로 컴포넌트 옵션을 정의하는 용도로 사용 가능
  • <script setup>Composition API 로직을 집중적으로 작성하는 영역

특별한 이유(예: 플러그인, class-style component, 옵션 병합 등)가 없다면

실무에서는 대부분 <script setup>만 쓴다고 생각해도 무방합니다.

 


 

7. <script setup> 사용 시 주의점 세 가지

1️⃣ 최상위에서만 Composition API 사용

  • ref, reactive, computed, watch, onMounted 같은 훅은 최상위 레벨에서 호출하는 것이 기본 패턴입니다.
  • <script setup> 안에서 if문/for문 안에서 훅을 호출하는 것은 피해야 합니다.

 


 

2️⃣ this 사용하지 않기

Composition API와 <script setup>에서는 this를 사용하지 않습니다.

  • this.count가 아니라 count.value
  • this.$router 대신 router = useRouter()

이 패턴은 Vue 3 전체에서 공통으로 적용되는 내용입니다.

 


 

3️⃣ export default, return 금지

<script setup> 안에서는 export default를 쓰지 않습니다.

또한, return { ... }도 하지 않습니다.

템플릿에서 쓸 값/함수는 그냥 최상위에 선언만 해두면 됩니다.

 


 

이런 제약 덕분에 컴파일러가 코드를 분석해서

더 강력한 최적화와 타입 추론을 할 수 있습니다.

 


 

8. 정리

  • <script setup> = Vue3 SFC에서 권장되는 Composition API 문법
  • export default, setup() 함수, return 없이도 템플릿에서 바로 사용 가능
  • props / emits / expose는 defineProps, defineEmits, defineExposes 매크로로 정의
  • 템플릿 쪽은 그대로 <template>, <style> 구조 유지
  • this와 return 사용하지 않음, Composition API 혹은 최상위에서 호출

이제 <script setup>까지 익혀두면 앞서 배운 코드를 전부 <script setup> 스타일로 자연스럽게 엮을 수 있게 됩니다.

 


 

출처

Vue 공식 문서 - 싱글 파일 컴포넌트, <script setup>

Vue 공식 문서 - 반응성 API: 코어

 

728x90
반응형