Vue

[Vue 3] [SFC] 2. <script setup>

머지?는 병합입니다 2023. 3. 20. 12:21

 

 

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>

 

 

이처럼 3번이나 써야한다는 거다.. 정말 귀찮음 그 잡채... 심지어 컴포넌트가 늘어나다 보면 심심찮게 위의 components나 composition의 return 에서 빼먹는다는거다.. component가 10개 이상 늘어나면 꼭 생긴다.. 진짜로...

 

위의 예시들을 보면 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