KB IT's Your Life/Vue

Vue [09] Composition API

지식보부상님 2025. 3. 27. 16:42

[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 옵션 사라지게 됨

import { ref } from 'vue';
export default {
  name: 'calc',
  setup() {
    const x = ref(10);
    const y = ref(20);
    return { x, y };
  },
};

 

◈ 프로젝트 시작하기

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>