Vue [09] Composition API
[1] Composition API란?
◈ Composition API
- 대규모 vue 애플리케이션의 컴포넌트 로직을 효과적으로 구성 및 재사용 가능하도록 하는 함수 기반 API
- Vue3에 새롭게 추가된 기능
- 과거에는 옵션 API (option API) 방식으로, data, methods, computed, watch와 같은 옵션들을 작성해야 했음
◈ Options API 방식
- 과거에 사용하던 방식으로 데이터 파트와 이를 처리하는 함수 파트 사이의 거리가 멀다는 단점
- 로직 재사용이 불편하다는 단점
[2] setup 메서드를 이용한 초기화
◈ setup() 메서드
- beforeCreate, created 단계에서 setup() 메서드가 호출됨
- 초기화 작업을 setup()에 정의함
- 반응형 데이터(proxy), 계산된 속성(computed), 메서드, 생명주기 훅을 객체 형태로 리턴
=> data, methods, computed 옵션 사라지게 됨
◈ 프로젝트 시작하기
npm init vue <폴더명>
cd <폴더명>
npm install
◈ setup() 메서드의 매개변수
- 2개의 인자 가짐
- 부모 컴포넌트로부터 전달받는 속성(props)
- 컴포넌트 컨텍스트(component context): 기존 옵션 API에서 this에 해당, vue 인스턴스 속성에 접근 가능 (ex) emit() )
[3] 반응성을 가진 상태데이터
◈ ref 함수를 이용한 상태 데이터
- data 옵션에 해당하며, 일반적으로 기본 데이터 타입에 대한 반응성 데이터를 생성
- 기본형 데이터(Primitive type; Number, Boolean, String)의 경우 반드시 ref 함수 이용해야
- 해당 데이터를 스크립트 파트에서 사용할 때는 x.value로 접근해야
- 템플릿 파트에서는 x로 접근 가능
◈ reactive()를 이용한 상태
- 참조 데이터 타입(배열, 객체)에 대한 반응형 데이터 생성 (기본형 데이터 불가)
=> 초기화는 reactive( { ...} ), reactive( [ ... ] ) 의 형태로만
- 수정 불가 => let 대신 const로 변수를 정의하는 것이 좋음
[4] computed()
◈ computed()
- 옵션 API에서 계산된 속성(computed 옵션)에 해당
import{ computed } from 'vue';
export default{
setup(){
const 속성명 = computed( ()=>{ ... return 값; }
}
});
[5] watch와 watchEffect
◈ watch
- watch() 함수 통해 제공
watch(data, (current, old) => { // 처리할 연산 })
- 첫번째 인자: data: 감시하려는 반응형 데이터, 속성, 계산된 속성 * watch(x.value,..) 로 쓰면 x, watch(x, ...) 로 써야
- 두번째 인자: 핸들러 함수: current(변경된 값), old(변경되기 전 값) * current, old는 ref.value에 해당하는값 (ref 객체x)
[6] 생명주기 훅
◈ Composition API의 생명주기
- beforeCreate, created 를 setup() 에서 처리
[7] <script setup> 사용하기
◈ <script setup>
- 단일 컴포넌트 내에서 Composition API 를 더 편리한 문법적 작성 기능 제공
- setup() 함수 내부 코드로 이용됨
- 장점
- 상용구 사용 ↓ => 간결한 코드 작성 가능
- 런타임 성능 ↑
- IDE 타입 추론 성능 ↑
- 순서 타입스크립트 언어 사용 => props, 이벤트 선언 가능
- 템플릿에서 사용하는 값: 최상위 변수, 함수는 직접 템플릿에서 사용 가능
- 지역 컴포넌트 등록: import만 하면 됨. components 속성 필요 없음
- 속성, 이벤트 처리
- 스크립트 파트에서 접근하기 위해선 props.todoItem으로, 템플릿 파트에서는 todoItem 으로 접근하면 된다.
[ computed, watch 예제 ]
src/components/Counter.vue
<!-- computed methods watch 예제 -->
<template>
<div>
<h1>Counter</h1>
<p>Count: {{ count }}</p>
<p>Double: {{ double }}</p>
<p>지금까지 계산한 횟수: {{ total }}</p>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
const count = ref(0);
const total = ref(0);
// computed
const double = computed(() => count.value * 2);
// method
function increment() {
count.value++;
total.value++;
}
function decrement() {
count.value--;
total.value++;
}
// watch
watch(count, (newValue, oldValue) => {
console.log(`카운트가 ${oldValue}에서 ${newValue}(으)로 변경됨`);
if (newValue % 10 === 0 && newValue !== 0) {
alert(`축하! ${newValue}도달!`);
}
});
</script>
<style lang="scss" scoped></style>
src/App.vue
<script setup>
import Counter from './components/Counter.vue';
</script>
<template>
<Counter />
</template>
[ props 예제 ]
src/components/propsChild.vue
<template>
<div>
<h1>{{ msg }}</h1>
</div>
</template>
<script setup>
// props 정의
const props = defineProps({
msg: {
type: String,
required: true,
},
});
// script level에서는 props.msg 로 작성해야 (.value 처럼)
console.log(props.msg);
</script>
<style lang="scss" scoped></style>
src/components/propsParent.vue
<template>
<div>
<PropsChild :msg="msg" />
</div>
</template>
<script setup>
import PropsChild from './PropsChild.vue';
import { ref } from 'vue';
const msg = ref('Props로 전달될 문자열');
</script>
<style lang="scss" scoped></style>
src/App.vue
<script setup>
import PropsParent from './components/PropsParent.vue';
</script>
<template>
<PropsParent />
</template>
[ emit 예제 ]
src/components/emitChild.vue
<template>
<div>
<input type="text" v-model.trim="inputMsg" />
<button @click="sendMsg">부모한테 전송</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const inputMsg = ref('');
const emit = defineEmits(['send-msg']);
const sendMsg = () => {
emit('send-msg', inputMsg.value);
};
</script>
<style lang="scss" scoped></style>
src/components/emitParent.vue
<template>
<div>
<h1>{{ msg }}</h1>
<EmitChild @send-msg="emitHandler" />
</div>
</template>
<script setup>
import EmitChild from './EmitChild.vue';
import { ref } from 'vue';
const msg = ref('자식이 바꿀 메세지');
function emitHandler(value) {
msg.value = value;
}
</script>
<style lang="scss" scoped></style>
src/App.vue
<script setup>
import EmitParent from './components/EmitParent.vue';
</script>
<template>
<EmitParent />
</template>
[ 종합 예제 ]


src/components/QuizChild.vue
<!-- QuizChild -->
<template>
<div>
<input v-model.number="inputNum" />
<button @click="sendMsg">정답!</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const props = defineProps({
answerNum: {
type: Number,
required: true,
},
});
const inputNum = ref(0);
const emit = defineEmits(['send-msg']);
const msg = ref('');
const cnt = ref(0);
const sendMsg = () => {
if (props.answerNum < inputNum.value) {
msg.value = 'DOWN';
} else if (props.answerNum > inputNum.value) {
msg.value = 'UP';
} else {
msg.value = '정답입니다!';
}
cnt.value++;
emit('send-msg', msg.value);
};
watch(cnt, (newValue, oldValue) => {
if (newValue === 3) {
alert(`3번째 시도입니다!`);
}
});
</script>
<style lang="scss" scoped></style>
src/components/QuizParent.vue
<!-- QuizParent -->
<template>
<div>
<h1>{{ msg }}</h1>
<QuizChild :answerNum="answerNum" @send-msg="emitHandler" />
</div>
</template>
<script setup>
import QuizChild from './QuizChild.vue';
import { ref } from 'vue';
const answerNum = ref(0);
answerNum.value = parseInt(Math.random() * 10);
const msg = ref('랜덤 숫자를 맞춰 주세요');
function emitHandler(value) {
msg.value = value;
}
</script>
<style lang="scss" scoped></style>
src/App.vue
<script setup>
import QuizParent from './components/QuizParent.vue';
</script>
<template>
<QuizParent />
</template>