
Vue3 컴포지션 API 반응성 오류 해결기
Vue3로 프로젝트를 진행하다 보면, 기존 Vue2에서 경험하지 못했던 새로운 개념들과 마주치게 됩니다. 그 중 하나가 바로 컴포지션 API(Composition API)인데요. 이 API는 코드의 재사용성과 명확성을 높여주지만, 반대로 처음 사용할 때는 오히려 예기치 못한 오류나 헷갈림을 유발하기도 합니다.
이번 글에서는 제가 실제로 Vue3 컴포지션 API를 사용하면서 겪은 "반응성 오류"에 대한 경험담을 공유하려고 합니다. 왜 그 오류가 발생했는지, 어떤 방식으로 해결했는지, 그리고 같은 문제를 다시 겪지 않기 위한 팁까지 모두 담아봤어요. Vue3를 사용하는 초보자부터 중급 개발자분들까지 실질적으로 도움을 받을 수 있도록 정리해보았습니다.
목차
📌 오류 발생 상황
제가 겪은 반응성 오류는 ref
와 reactive
를 함께 사용할 때 발생했습니다. 프로젝트에서는 사용자 정보를 관리하는 객체가 있었고, 이 객체를 여러 컴포넌트에서 공유해야 했습니다. 그래서 reactive
로 만든 객체를 props로 전달하고 있었죠.
예를 들어, 다음과 같은 코드를 사용했어요:
const user = reactive({
name: 'John',
age: 30
});
그리고 이 데이터를 다른 컴포넌트에서 받았을 때, 어떤 이유에서인지 값이 업데이트되지 않거나, watch가 동작하지 않는 현상이 발생했습니다. 처음엔 단순한 실수일 거라 생각했지만, 구조를 아무리 바꿔도 반응하지 않는 데이터에 당황했죠.
📌 문제 원인 분석
이 문제의 핵심은 reactive 객체를 구조 분해 할당하거나 직접 접근했을 때 반응성이 깨진다는 점이었습니다. 특히 아래와 같은 실수가 흔한데요:
const { name } = user;
console.log(name); // 여기서부터 반응성이 사라짐
이렇게 구조 분해를 하면 name
은 더 이상 반응형 데이터가 아닙니다. 즉, user.name
을 직접 참조해야만 Vue의 반응 시스템이 추적할 수 있어요. 이런 개념을 몰랐던 초기에는 왜 watch나 computed가 동작하지 않는지 이해할 수 없었죠.
🔍 Point: 구조 분해를 할 때는 ref로 래핑하거나, 반응형 객체에서 값을 직접 꺼내 쓰는 것에 주의가 필요합니다.
📌 해결 과정
이 문제를 해결하기 위해 우선적으로 했던 일은 구조 분해 사용을 피하고 객체 자체를 유지하는 것이었어요. 예를 들어:
watch(() => user.name, (newVal) => {
console.log('이름이 변경되었습니다:', newVal);
});
그리고 나중에는 사용자 데이터를 관리하는 별도의 store
역할을 하는 composable 함수를 만들어 공유하게 되었고, 이 안에서는 ref
와 reactive
를 조화롭게 사용했습니다.
import { reactive, toRefs } from 'vue';
export function useUser() {
const state = reactive({
name: 'John',
age: 30
});
return {
...toRefs(state)
};
}
이렇게 하면 반응성도 유지하면서 구조 분해도 안정적으로 사용할 수 있더라고요.
📌 재발 방지를 위한 팁
- 구조 분해할 땐 toRefs를 사용하자:
reactive
객체에서 구조 분해가 필요할 경우toRefs()
로 안전하게 꺼내는 습관을 들이세요. - ref와 reactive는 혼용 주의: 같은 데이터라도 상황에 따라 어떤 형태가 적합한지 구분하는 것이 중요합니다.
- watch 대상은 함수 형태로: 객체 속성을 감시할 땐 항상 함수로 감싸서 추적 대상을 명확히 해야 합니다.
- 컴포지션 API에 대한 이해도 향상: 공식 문서를 읽고 기본 예제부터 구현해보는 게 가장 좋습니다. 처음부터 완벽하게 하려 하지 말고, 오류를 통해 배우세요!
📌 추가적인 경험과 팁
Vue3를 본격적으로 사용하기 시작한 이후, 반응성 관련된 오류는 생각보다 자주 마주쳤습니다. 특히 컴포지션 API로 코드 구조가 바뀌면서, 기존 Options API에 익숙했던 제게는 낯설고 헷갈리는 부분이 많았어요. 그래서 이 부분에 대해 제가 실제로 겪은 상황과 깨달음을 바탕으로, 더 깊이 있는 팁을 나눠보려 합니다.
1. ref 객체는 언제 써야 할까?
처음에는 모든 상태를 reactive
로 감싸면 되는 줄 알았지만, 단일 값(primitive) 상태를 관리할 땐 ref
가 훨씬 간단하고 직관적이었어요. 예를 들어, 모달의 표시 여부, 숫자 카운터 등의 상태는 이렇게 관리했습니다:
const isOpen = ref(false);
function toggleModal() {
isOpen.value = !isOpen.value;
}
ref
는 .value
로 접근해야 해서 처음엔 어색했지만, 나중엔 이게 오히려 명확한 느낌이더라고요. reactive
는 객체형 데이터에만 쓰는 게 훨씬 관리하기 편했습니다.
2. 템플릿에서 구조 분해는 주의!
템플릿 바인딩 시에는 구조 분해를 하지 않는 것이 좋습니다. 이전에는 다음과 같이 썼지만,
// ❌ 이렇게 쓰면 반응성 깨짐
const { count } = useCounter();
이후에는 이렇게 toRefs
나 return { count }
형식으로 처리해서 템플릿에서도 반응성을 유지할 수 있게 했습니다:
// ✅ 이렇게 쓰자
const state = useCounter();
// 템플릿에서 state.count.value 사용
3. defineExpose와 defineProps도 익숙해지자
컴포지션 API에서 컴포넌트 간 데이터 공유를 할 때는 defineExpose
나 defineProps
사용도 핵심이었어요. setup()
내부에서 선언하는 이 도구들 덕분에 코드 가독성이 좋아졌고, 재사용도 쉬워졌죠.
4. vscode에서 Volar는 필수!
반응성 오류를 빨리 파악하려면 도구 세팅도 중요합니다. 특히 Volar 익스텐션은 Vue3 컴포지션 API를 쓸 때 타입 추론을 잘해줘서 개발 속도를 정말 많이 높여줬어요. 기존 Vetur와는 차원이 다릅니다!
5. console.log도 구조 분해 주의
이건 사소하지만, console.log(user.name)
대신 console.log(user)
전체 객체를 찍는 습관이 도움이 됐어요. 종종 반응형 객체가 Proxy로 감싸져 있어서 개별 속성만 찍으면 이상하게 보이는 경우도 있었거든요.
실전 팁 요약: Vue3에서 반응성 오류를 줄이려면 ref vs reactive
구분 명확히 하고, 구조 분해 대신 toRefs
, setup 함수의 반환값
, console 로그 활용
등을 적극적으로 활용하세요!
그리고 마지막으로, 제가 정말 도움이 많이 됐던 방법은 "문제 해결 과정을 블로그에 정리해보는 것"이었어요. 누군가에게 설명하듯 기록하면 내 코드에 어떤 맥락이 있는지 명확해지고, 나중에 비슷한 문제가 생겼을 때 바로 복습할 수 있더라고요. 실제로 이 글도 그런 기록 중 하나예요 😊
📌 결론
Vue3의 컴포지션 API는 분명 강력한 도구입니다. 하지만 강력한 만큼, 익숙해지기 전까지는 많은 시행착오를 겪게 되죠. 이번 글에서 다룬 반응성 오류 또한 그런 과정 중 하나였습니다. 처음에는 왜 반응하지 않는지조차 몰랐지만, 경험을 통해 어떤 방식이 올바른 접근인지 체득하게 되었어요.
컴포지션 API에서 ref
와 reactive
의 차이점, 구조 분해의 한계, watch
와 computed
의 올바른 사용법 등을 하나씩 익히다 보면 자연스럽게 Vue3에 대한 이해도가 높아지게 됩니다. 실수를 통해 배우는 것도 개발자의 성장 과정 중 중요한 부분이니까요 😊
이 글을 읽고 계신 여러분도 혹시 비슷한 문제를 겪고 있다면, 제 경험이 조금이나마 도움이 되었으면 좋겠습니다. 앞으로도 Vue3로 멋진 프로젝트를 개발하시길 응원합니다.!