Vue 문법 정리 2 / Vue 3 Composition API 종류 및 사용법
setup()
컴포넌트 인스턴스가 생성되기 전 호출되는 함수이며, Composition API의 진입점입니다.
setup 함수 사용 예시
<template>
<p>
{{ counter }}
</p>
<button @click="increment">click!</button>
</template>
<script>
import { ref } from 'vue';
export default {
setup(props, context) {
const counter = ref(0); // 초기값 0 세팅
const increment = () => {
counter.value++;
}
return {
counter,
increment
}
},
mounted() {
console.log(this.count);
}
}
</script>
setup 함수 내에서 정의한 반응형 상태, 함수를 return 하면 template에서 사용할 수 있습니다.
mounted() 같은 Options API 함수 내에서도 this 키워드로 컴포넌트 인스턴스에 접근할 수 있습니다.
Reactivity
상태를 반응형으로 만들어 데이터 변경 시 UI가 자동 업데이트 되도록 돕는 반응형 API들입니다.
ref()
<template>
<button v-on:click="addMessage">Click : {{ message }}</button>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
let message = ref('Hello Vue!');
const addMessage = () => {
message.value = message.value + '!';
}
return {
message,
addMessage
}
}
}
</script>
String, Number, boolean 등 단일 프리미티브 값을 반응형 객체로 생성하는 API입니다.
프리미티브 값을 레퍼런스 객체로 매핑해서 .value로 값에 접근할 수 있습니다.
Vue 3.3 이상에서 $ref()로 선언하면 .value 없이도 값에 바로 접근할 수 있습니다.
reactive()
<template>
<button v-on:click="increment">Click : {{ state.count }}</button>
<button v-on:click="increment">Deep Click : {{ state.deep.count }}</button>
</template>
<script>
import { reactive } from 'vue';
export default {
setup() {
const state = reactive({
count: 0,
deep: {
count: 0,
}
});
const increment = () => {
state.count++;
state.deep.count++;
}
return {
state,
increment,
}
}
}
</script>
객체, 배열 등 레퍼런스 타입을 깊은 반응형 객체로 생성하는 API입니다.
프리미티브 타입으로 만들면 반응형이 동작하지 않습니다.
<script>
import { reactive, ref } from 'vue';
export default {
setup() {
const count = ref(0);
const state = reactive({
count, // count: count 단축
});
// 동일 값 출력
console.log(count.value);
console.log(state.count);
// undefined
console.log(state.count.value);
return {};
}
}
</script>
위와 같이, ref로 선언한 데이터를 reactive 객체의 속성으로 주입하면 .value가 자동으로 언래핑됩니다.
ref로 선언한 데이터를 reactive 배열의 요소로 주입하면 .value를 붙여서 접근해야 합니다.
toRef()
<templeat>
<div>
<p>{{ author }}</p>
</div>
</templeat>
<script>
import { reactive, toRef } from 'vue';
export default {
setup() {
const book = reactive({
author: 'Vue Team',
year: '2020',
title: 'Vue 3 Guide',
description: '책 설명',
price: '무료',
});
// 반응형 유지하며 구조분해 할당
const author = toRef(book, 'author');
// 반응형 유지 안되는 구조분해 할당 예시
// const author = book;
return { author };
}
}
</script>
reactive 객체의 단일 속성을 ref 형태로 반환해서 반응형을 유지하며 구조분해 할당하는 API입니다.
toRef 또는 toRefs를 사용하지 않고 ES6 비구조화 할당으로 추출 시 반응성을 잃게 됩니다.
toRefs()
<templeat>
<div>
<p>{{ author }}</p>
<p>{{ title }}</p>
</div>
</templeat>
<script>
import { reactive, toRefs } from 'vue';
export default {
setup() {
const book = reactive({
author: 'Vue Team',
year: '2020',
title: 'Vue 3 Guide',
description: '책 설명',
price: '무료',
});
// 반응형 유지하며 구조분해 할당
const { author, title } = toRefs(book);
// 반응형 유지 안되는 구조분해 할당 예시
// const { author, title } = book;
return { author, title, book };
}
}
</script>
reactive 객체의 모든 속성을 각각 ref 형태로 반환해서 반응형을 유지하며 구조분해 할당하는 API입니다.
readonly()
<script>
import { reactive, readonly } from 'vue';
const original = reactive({ count: 0 });
const copy = readonly(original);
// 원본 변경 시 복사본도 변경 (최신 상태 유지)
original.count++;
// 복사본 변경 시 콘솔 에러 및 실패
copy.count++;
</script>
반응형 객체를 읽기 전용으로 만들어서, 복사본을 통한 상태 변경을 방지하는 API입니다.
computed()
<template>
<p>{{ reversedMessage }}</p>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const message = ref('Hello Vue!');
const reversedMessage = computed(() =>
message.value.split('').reverse().join('') // 리턴 값 (한 줄이어서 중괄호, return 생략)
);
return { reversedMessage };
}
}
</script>
템플릿 문법 내 계산식이 길어질 때 computed() 함수로 변경하면 좋습니다.
계산한 결과 값을 캐시에 저장 후 반환하므로, 반복 호출 시 일반 함수보다 효율적입니다.
의존하는 반응형 데이터가 변경되면 다시 계산해서 최신 값을 유지합니다.
<template>
<p>{{ fullName }}</p>
</template>
<script>
import { ref, computed } from 'vue';
const firstName = ref('홍');
const lastName = ref('길동');
const fullName = computed({
get() {
return firstName.value + lastName.value;
},
set(value) {
// 이름 배열을 구조분해 할당
[ firstName.value, lastName.value ] = value.split(' ');
},
});
// setter가 없으면 값 변경 시 에러
fullName.value = '송 하영';
</script>
computed 함수로 반환한 value는 읽기 전용이라서 값 변경 시 에러가 납니다.
위와 같이 getter/setter를 정의하면 쓰기 가능한 computed가 됩니다.
Watch
반응형 데이터 변경 시 콜백 함수를 실행하는 API입니다.
DOM 변경, 비동기 작업, 사이드 이펙트 등을 수행할 수 있습니다.
Watch 사용 예시
const data변수명 = ref('텍스트');
watch(data변수명, (newValue, oldValue) => {
// 데이터 변경 시 특정 작업 수행
});
const data변수명1 = ref('텍스트');
const data변수명2 = ref(숫자);
watch([ data변수명1, data변수명2 ], (newValues, oldValues) => {
// 배열 내 데이터 변경 시 특정 작업 수행
});
const obj = reactive({
count: 0,
name: '송하영',
hobby: '주식',
});
watch(obj, (newValue, oldValue) => {
// 객체 내 모든 속성 변경 시 특정 작업 수행
}, { deep: true });
const obj = reactive({
count: 0,
});
watch(() => obj.count, (newValue, oldValue) => {
// getter 함수로 받은 obj.count 변경 시에만 특정 작업 수행
});
const data변수명 = ref('텍스트');
watch(data변수명, (newValue, oldValue) => {
// 처음 렌더링 시 즉시 실행, 데이터 변경 시 특정 작업 수행
}, { immediate: true });
WatchEffect
콜백함수에서 사용된 반응형 데이터들 변경 시 실행되는 API입니다.
Watch와 달리, 렌더링 시 최초 한번 즉시 실행됩니다.
const data변수명 = ref('텍스트');
watchEffect(() => {
// WatchEffect 내에서 사용된 반응형 데이터 변경 시 특정 작업 수행
console.log(data변수명.value);
});
Lifecycle Hooks
Vue 컴포넌트 인스턴스 생성 후 거치는 라이프사이클 단계에 따라 호출되는 함수들입니다.
컴포넌트 라이프사이클 단계
create | 컴포넌트 인스턴스 생성 (초기화) |
mount | DOM에 컴포넌트 마운트(부착) 후 렌더링 |
update | 반응형 상태 변경 시 컴포넌트 업데이트(재렌더링) |
unmount | 컴포넌트 소멸 |
Lifecycle Hooks 종류
Options API | Composition API | 호출 시점 및 설명 |
---|---|---|
beforeCreate | (Setup 함수로 대체) |
컴포넌트 인스턴스 생성 전 호출 아직 this, DOM 요소를 사용할 수 없습니다. |
created | (Setup 함수로 대체) |
컴포넌트 인스턴스 생성 후 호출 created 훅은 this 및 data 등에 접근할 수 있어 반응형 상태 사용이 가능합니다. setup 함수 내에서는 아직 this, DOM 요소를 사용할 수 없습니다. |
beforeMount | onBeforeMount |
DOM에 컴포넌트가 마운트(부착) 되기 전 호출 DOM 요소 접근 시 null이 출력됩니다. |
mounted | onMounted |
컴포넌트가 DOM에 마운트(부착) 된 후 호출 Options API에서는 this로 app 인스턴스에 접근할 수 있습니다. 이제 DOM 요소 접근이 가능합니다. |
beforeUpdate | onBeforeUpdate | 반응형 상태 변경 시 DOM 업데이트(재렌더링) 전 호출 |
updated | onUpdated | DOM 업데이트(재렌더링) 완료 후 호출 |
beforeUnmount | onBeforeUnmount |
컴포넌트가 제거되기 전 호출 아직 DOM 요소 접근이 가능합니다. |
unmounted | onUnmounted |
컴포넌트가 제거된 후 호출 DOM 요소 접근 시 null이 출력됩니다. |
errorCaptured | onErrorCaptured | 자식 컴포넌트에서 에러 발생 시 호출 |
renderTracked | onRenderTracked | 반응형 데이터 의존성 추적 시 호출 (디버깅 용도) |
renderTriggered | onRenderTriggered | 반응형 데이터 변경으로 컴포넌트 렌더링 트리거 시 호출 (디버깅 용도) |
activated | onActivated | keep-alive 컴포넌트 활성화 시 호출 |
deactivated | onDeactivated | keep-alive 컴포넌트 비활성화 시 호출 |
serverPrefetch | onServerPrefetch |
SSR(서버 사이드 렌더링) 시 호출 데이터 사전 로딩용입니다. |
Vue 컴포넌트 인스턴스 생명 주기에서 실행되는 함수들입니다.
Options API Lifecycle Hooks 사용 예시
<script>
export default {
data() {
return {
data변수명: '텍스트',
};
},
setup() {
console.log('setup 호출 : this = undefined');
return {};
},
beforeCreate() {
console.log('beforeCreate 호출 : ' + this.data변수명 + ' = undefined');
},
created() {
console.log('created 호출 : ' + this.data변수명 + ' = 접근 가능');
},
}
</script>
setup() → beforeCreate() → created() 순으로 라이프사이클 훅이 실행됩니다.
Composition API Lifecycle Hooks 사용 예시
<script>
export default {
setup() {
console.log('setup 호출');
onBeforeMount(() => {
console.log('onBeforeMount 호출');
});
onMounted(() => {
console.log('모든 자식 컴포넌트 onMounted 후');
console.log('onMounted 호출');
});
onUnmounted(() => {
console.log('onUnmounted 호출');
});
return {};
},
}
</script>
setup() → onBeforeMount() → onMounted() 순으로 라이프사이클 훅이 실행됩니다.
Template Refs
template DOM 요소에 직접 접근할 때 사용합니다.
ref()와 함께 사용되며, setup() 함수 안에서 정의합니다.
자식 컴포넌트 DOM 요소를 ref로 참조하는 것은 컴포넌트 간 의존성을 높여서 권장되지 않습니다.
자식 컴포넌트에서 부모 컴포넌트에 접근할 수 있는 $parent 내장 객체도 권장되지 않습니다.
부모/자식 컴포넌트 상호작용은 props, emit으로 구현하는 것이 좋습니다.
Template Refs 사용 예시
<template>
// DOM 요소를 ref 변수에 추가
<input ref="inputRef" />
<button @click="focusInput">Focus</button>
// input DOM 출력
<p>{{ inputRef }}</p>
// input DOM 값 출력
// 마운트 전까지 inputRef는 null이기 때문에 v-if 없으면 에러
<p v-if="inputRef">{{ inputRef.value }} 또는 {{ $refs.inputRef.value }}</p>
</template>
<script>
import { ref, onMounted } from 'vue';
export default {
setup() {
// 템플릿 ref 변수 선언
const inputRef = ref(null);
const focusInput = () => {
// DOM 요소에 직접 접근
inputRef.value.focus();
};
onMounted(() => {
// 컴포넌트 마운트 후, DOM 요소 및 값 출력
console.log(inputRef.value);
console.log(inputRef.value.value);
});
return { inputRef, focusInput };
},
};
</script>
setup 함수 내에서 input 요소에 접근하는 Template ref 사용 예시입니다.
DOM ref 속성 값과 return 반응형 상태 객체 키를 동일하게 선언해야 합니다.
Template Refs 배열 바인딩 예시
<template>
<ul>
// v-for문으로 출력하며 DOM 요소를 ref 배열에 추가
<li v-for="fruit in fruits" :key="fruit" ref="itemRefs">{{ fruit }}</li>
</ul>
</template>
<script>
import { ref, onMounted } from 'vue';
export default {
setup() {
onMounted(() => {
// ref 배열 내 DOM 요소들 출력
itemRefs.value.forEach(item => console.log(item));
// ref 배열 내 DOM 요소들 값 출력
itemRefs.value.forEach(item => console.log(item.textContent));
});
const fruits = ref(['사과', '딸기', '포도']);
const itemRefs = ref([]);
return { fruits, itemRefs };
},
};
</script>
위와 같이, v-for문과 ref 속성을 이용해서 DOM 요소들을 ref 배열에 추가할 수도 있습니다.
Template Refs 함수 바인딩 예시
<template>
<ul>
// 현재 DOM 엘리먼트를 받아서 내부 값을 ref 배열에 추가
<li v-for="(fruit, i) in fruits" :key="fruit" :ref="(el) => el && (itemRefs.value[i] = el.textContent)">{{ fruit }}</li>
</ul>
</template>
<script>
import { ref, onMounted } from 'vue';
export default {
setup() {
onMounted(() => {
// ref 배열 내 값 출력
itemRefs.value.forEach(item => console.log(item));
});
const fruits = ref(['사과', '딸기', '포도']);
const itemRefs = ref([]);
return { fruits, itemRefs };
},
};
</script>
위와 같이, v-for문으로 출력한 요소 내부 값을 ref 배열에 추가할 수도 있습니다.