[Vue 3] [SFC] 2. <script setup>
Vue 3.1에서 3.2로의 마이너 업데이트를 하면서 , TypeScript 환경과 더욱 간단한 composition api 를 사용하도록
등장한게 script setup 구문이다. (하지만 ts를 사용하지 않아도 script setup 은 사용가능하다)
일반 <script> 구문에 비해 여러 가지 이점을 제공하는 데 일단 간편하다는 점이다.
우선 기존의 composition api 는 ...
<script>
import { ref } from 'vue';
import CustomComponent from './CustomComponent.vue'; // 가상 컴포넌트
export default {
setup() {
const counter = ref(0);
const incrementCounter = () => counter.value++;
return {
counter,
incrementCounter,
CustomComponent
};
}
};
</script>
이에 비해 script setup 은
<script setup>
import { ref } from 'vue';
import CustomComponent from './CustomComponent.vue'; // 가상 컴포넌트
const counter = ref(0);
const incrementCounter = () => counter.value++;
</script>
깔끔하다... 특히 기존 compositions api 에서도 귀찮았던건
아래 options 처럼 결국 같은 compnents를 세번이나 연달아 써줘야한다는 거다...
단 숙지해야 할 점은 기존 composition api 처럼 export defaut 로
모든 변수와 메서드를 공개적으로 열어놓는것이 아니어서
부모 컴포넌트에서 임포트해서 사용할 때는 defineExpose 안에서 아래처럼 변수나 메서드 이름을 적어줘야 한다.
<script setup>
import { ref } from 'vue';
import CustomComponent from './CustomComponent.vue'; // 가상 컴포넌트
const counter = ref(0);
const incrementCounter = () => counter.value++;
defineExpose({
counter,
incrementCounter
});
</script>
위의 예시들을 보면 script setup을 쓰고 싶은 욕구가 샘 솟지 않는가??
지금 현 페이지는 초안으로 작성한 vue 초보때의 한글 번역을 하며 작성한 페이지이다
조금씩 수정해나갈 생각이지만.... 언제 완성될 진 모르겠다
특히 온라인 강의의 경우... defineProps defineEmit 정도로 끝나버린다
define 가족들을 하나씩 업데이트 할 계획이긴하다....
다른 장점은 아래 처럼 참조 함수에 전달한 값만 보고 변수의 유형을 추측하므로 변수의 유형을 지정하지 않더라도 커서를 변수 위에 올려놓으면 IDE가 유형 정보를 표시할 수 있다.
# 기본 문법
구문을 선택 하려면 <script> 블록에 setrup 속성을 추가한다:
<script setup>
console.log('hello script setup')
</script>
내부 코드는 컴포넌트의 setup() 함수의 내용으로 컴파일된다.
컴포넌트를 처음 가져올 때 한 번만 실행되는 일반 <script>와 달리 <script setup> 내부의 코드는
컴포넌트의 인스턴스가 생성될 때마다 실행되며, 내부 코드는 컴포넌트의 setup() 함수의 내용으로 컴파일됨.
즉, 컴포넌트를 처음 가져올 때 한 번만 실행되는 일반 <script>와 달리
<script setup> 내부의 코드는 컴포넌트의 인스턴스가 생성될 때마다 실행된다.
최상위 바인딩의 템플릿 노출
<script setup> 을 사용하는 경우 <script setup> 내에서 선언된 모든 최상위 바인딩
(변수, 함수 선언 및 가져오기 포함)을 템플릿에서 직접 사용할 수 있다 :
<script setup>
// variable
const msg = 'Hello!'
// functions
function log() {
console.log(msg)
}
</script>
<template>
<button @click="log">{{ msg }}</button>
</template>
import도 같은 방식으로 노출된다.
즉, 메서드 옵션을 통해 노출할 필요 없이 가져온 헬퍼 함수를 템플릿 표현식에서 직접 사용할 수 있다:
<script setup>
import { capitalize } from './helpers'
</script>
<template>
<div>{{ capitalize('hello') }}</div>
</template>
# 반응성
반응형 상태는 API를 사용하여 명시적으로 생성해야 한다. setrup() 함수에서 반환된 값과 유사하게,
템플릿에서 참조할 때 참조가 자동으로 언래핑된다.
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
# 컴포넌트에서의 사용
<script setup> 스코프 안의 값은 사용자 정의 컴포넌트 태그 이름으로 직접 사용할 수도 있다.
<script setup>
import MyComponent from './MyComponent.vue'
</script>
<template>
<MyComponent />
</template>
MyComponent 를 변수로 참조한다고 생각하면 된다.
케밥 케이스에 해당하는 <my-component>도 템플릿에서 사용할 수 있지만
일관성을 위해 파스칼 케이스 컴포넌트 태그를 강력히 권장합니다.(Vue3 공식 권장)
또한 네이티브 사용자 정의 요소와 구별하는 데 도움이 된다.
동적 컴포넌트
컴포넌트는 문자열 키에 등록되는 대신 변수로 참조되므로
<script setup> 내에서 동적 컴포넌트를 사용할 때는 동적인 :is 바인딩을 사용해야 한다:
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>
컴포넌트를 삼항식 표현식에서 변수로 사용할 수 있는 방법에 주목.
재귀 컴포넌트
SFC는 파일명을 통해 암시적으로 자신을 참조할 수 있다.
예를 들어 FooBar.vue라는 파일은 템플릿에서 <FooBar/>로 자신을 참조할 수 있음.
이는 임포트된 컴포넌트보다 우선순위가 낮다는 점에 유의해야 한다.
컴포넌트의 유추된 이름과 충돌하는 명명된 임포트가 있는 경우 임포트에 별칭을 지정할 수 있다:
import { FooBar as FooBarChild } from './components'
네임 스페이스 컴포넌트
<Foo.Bar>와 같이 점이 있는 컴포넌트 태그를 사용하여 객체 속성 아래에 중첩된 컴포넌트를 참조할 수 있다.
이는 단일 파일에서 여러 컴포넌트를 가져올 때 유용하다.
<script setup>
import * as Form from './form-components'
</script>
<template>
<Form.Input>
<Form.Label>label</Form.Label>
</Form.Input>
</template>
# Custom Derective 사용 ( 사용자 정의 지시어)
전역으로 등록된 사용자 정의 지시어는 정상적으로 작동한다.
로컬 사용자 정의 지시문은 <script setup>에 명시적으로 등록할 필요는 없지만
명명체계인 vNameOfDirective를 따라야 한다: (v-my-directive)
<script setup>
const vMyDirective = {
beforeMount: (el) => {
// do something with the element
}
}
</script>
<template>
<h1 v-my-directive>This is a Heading</h1>
</template>
다른 곳에서 지시문을 가져오는 경우 필요한 명명 체계에 맞게 이름을 변경할 수 있다:
<script setup>
import { myDirective as vMyDirective } from './MyDirective.js'
</script>
개인적으로 script setup 구문을 사용하면서 제일 적응이 안됬던 게
이 define 식구들이었다.
이 define 은 크게
# defineProps( ) & defineEmits( )
완전한 유형 추론을 지원하는 props 및 emits와 같은 옵션을 선언하려면
<script setup> 내에서 자동으로 사용할 수 있는 defineProps 및 defineEmits API를 사용하면 된다:
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
// setup code
</script>
- defineProps 및 defineEmits는 <script setup> 내에서만 사용할 수 있는 컴파일러 매크로입니다. 이 매크로는 가져올 필요가 없으며 <script setup>이 처리될 때 컴파일 된다.
- defineProps는 props 옵션과 동일한 값을 허용하고, defineEmits는 emits 옵션과 동일한 값을 허용한다.
- defineProps와 defineEmits는 전달된 옵션에 따라 적절한 유형 추론을 제공한다.
- defineProps 및 defineEmits에 전달된 옵션은 설정에서 모듈 scope로 들어감.
따라서 옵션은 설정 범위에서 선언된 로컬 변수를 참조할 수 없다. 그렇게 하면 컴파일 오류가 발생하게 됨.
하지만 가져온 바인딩은 모듈 범위에도 있으므로 참조할 수 있다.
TypeScript를 사용하는 경우 순수 타입 어노테이션을 사용하여 prop와 emit을 선언할 수도 있음.
# defineExpose()
<script setup>을 사용하는 컴포넌트는 기본적으로 닫혀 있습니다.
즉, 템플릿 참조 또는 $parent 체인을 통해 검색되는 컴포넌트의 공개 인스턴스는 <script setup> 내부에 선언된 바인딩을 노출하지 않습니다.
<script setup> 컴포넌트에서 프로퍼티를 명시적으로 노출하려면 defineExpose 컴파일러 매크로를 사용하세요:
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
부모의 템플릿 참조를 통해 이 컴포넌트의 인스턴스를 가져올 때,
검색된 인스턴스는 { a: 숫자, b: 숫자 } 형태가 됩니다.
(refs 는 일반 인스턴스와 마찬가지로 자동으로 언래핑됩니다).
# useSlots( ) & useAttrs( )
템플릿에서 $slots 및 $attrs로 직접 액세스할 수 있으므로
<script setup> 내에서 슬롯 및 attrs를 사용하는 경우는 비교적 드물어야 한다.
드물게 필요한 경우에는 각각 사용 슬롯 및 사용 속성 헬퍼를 사용하세요:
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>
useSlots와 useAttrs는 setupContext.slots 및 setupContext.attrs와 동등한 값을 반환하는 실제 런타임 함수입니다.
일반 컴포지션 API 함수에서도 사용할 수 있습니다.
# 일반 <script >와 함께 사용
<script setup>은 일반 <script >와 함께 사용할 수 있습니다.
필요한 경우 일반 <script >가 필요할 수 있습니다:
- 다음과 같은 경우 일반 <script>로 표현할 수 없는 옵션(예: inheritAttrs 또는 플러그인을 통해 활성화된 사용자 정의 옵션)을 선언해야 합니다.
- 명명된 내보내기 선언.
- 사이드 이펙트를 실행하거나 한 번만 실행해야 하는 객체를 생성합니다.
<script>
// 모듈 스코프에서 실행되는 일반 <script>(한 번만)
runSideEffectOnce()
// 부가 옵션 선언
export default {
inheritAttrs: false,
customOptions: {}
}
</script>
<script setup>
// setup() 스코프에서 실행됩니다(각 인스턴스에 대해).
</script>
동일한 컴포넌트에서 <script setup>과 <script >를 결합하는 지원은 위에서 설명한 시나리오로 제한됩니다.
구체적으로 :
- props 및 emits 같이 <script setup>을 사용하여 이미 정의할 수 있는 옵션에는 별도의 <script> 섹션을 사용하지 마세요.
- <script setup> 내에서 생성된 변수는 컴포넌트 인스턴스에 프로퍼티로 추가되지 않으므로 옵션 API에서 액세스할 수 없습니다. 이러한 방식으로 API를 혼합하는 것은 강력히 권장하지 않습니다.
지원되지 않는 시나리오 중 하나에 해당하는 경우 <script setup>을 사용하는 대신 명시적 setup() 함수로 전환하는 것을 고려해야 합니다.
# Top-level await
최상위 await 함수는 <script setup> 내에서 사용할 수 있습니다.
결과 코드는 async setup()으로 컴파일됩니다:
<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>
또한 대기된 표현식은 대기 후 현재 컴포넌트 인스턴스 컨텍스트를 보존하는 형식으로 자동으로 컴파일됩니다.
async setup()은 현재 아직 실험적인 기능인 Suspense와 함께 사용해야 합니다.
향후 릴리스에서 마무리하여 문서화할 계획이지만, 지금 궁금하신 분은 테스트를 참조하여
어떻게 작동하는지 확인하실 수 있습니다.
제한 사항
모듈 실행 시맨틱의 차이로 인해 <script setup> 내부의 코드는 SFC의 컨텍스트에 의존합니다.
외부 .js 또는 .ts 파일로 이동하면 개발자와 도구 모두에 혼란을 초래할 수 있습니다.
따라서 <script setup>은 src 속성과 함께 사용할 수 없습니다.
출처 : https://vuejs.org/api/sfc-script-setup.html