Vue 3 실무 개발을 위한 반응성 핵심 가이드

Vue 3 실무 개발을 위한 반응성 핵심 가이드 관련 이미지
Photo by Boskampi via Pixabay

Vue는 화면과 데이터를 연결하는 문법이 직관적이어서 웹 개발 입문자가 빠르게 결과를 만들 수 있습니다. 그러나 규모가 커지면 반응성, 컴포넌트 통신, 비동기 처리, 상태 관리의 경계를 이해하지 못해 예측하기 어려운 버그가 생깁니다. Vue 3의 Composition API를 잘 쓰는 핵심은 기능을 한 파일에 몰아넣는 것이 아니라 상태의 소유자와 변경 경로를 명확하게 만드는 것입니다.

ref와 reactive의 차이 이해하기

ref는 숫자와 문자열을 포함한 모든 값을 감싸며 자바스크립트 코드에서는 .value로 접근합니다. 템플릿에서는 자동으로 풀리므로 이름만 사용할 수 있습니다. reactive는 객체를 Proxy로 감싸 깊은 속성 변경을 추적합니다. 객체를 구조 분해하면 반응성 연결이 끊길 수 있으므로 toRefs를 사용하거나 원래 객체의 속성으로 접근해야 합니다.

어느 쪽이 무조건 더 좋은 것은 아닙니다. 독립적인 값과 교체될 수 있는 객체는 ref로 통일하면 규칙이 단순하고, 함께 움직이는 폼 상태는 reactive가 읽기 편할 수 있습니다. 팀 안에서 선택 기준을 정하고 한 컴포넌트에서 이유 없이 두 방식을 섞지 않는 것이 좋습니다.

computed와 watch의 역할 나누기

기존 상태에서 계산할 수 있는 값은 computed로 표현합니다. 장바구니 합계처럼 입력이 같으면 결과가 같은 파생 상태를 별도 ref에 복사하고 watch로 동기화하면 두 값이 어긋날 가능성이 생깁니다. computed는 의존성이 바뀔 때만 다시 계산되고 캐시됩니다.

watch는 API 호출, 로컬 저장소 기록, 라우터 이동처럼 외부 부수 효과가 필요할 때 사용합니다. 검색어를 감시해 서버 요청을 보내는 경우 디바운스를 적용하고 이전 요청을 취소해야 느린 응답이 최신 결과를 덮지 않습니다. 깊은 객체 전체를 deep: true로 감시하면 비용이 커질 수 있으므로 필요한 속성만 명시합니다.

구분 기준: 화면에 표시할 값을 계산한다면 computed, 상태 변화로 외부 행동을 실행한다면 watch를 먼저 고려합니다.

props는 읽고 이벤트로 변경 요청하기

부모가 전달한 props를 자식이 직접 수정하면 데이터 흐름을 추적하기 어려워집니다. 자식은 props를 읽고 변경이 필요하면 emit으로 의도를 알립니다. 양방향 입력은 v-model 규약을 사용하되 어떤 값이 변경되는지 이벤트 이름을 분명하게 정합니다. 객체 props는 내부 속성을 바꿀 수 있어도 부모 상태를 우회해서 수정하는 결과가 되므로 복사본을 만들거나 변경 이벤트를 보내는 편이 안전합니다.

컴포넌트 설계 체크포인트

  • 한 컴포넌트가 하나의 명확한 화면 책임을 갖게 합니다.
  • props에는 가능한 자료형과 필수 여부, 기본값을 정의합니다.
  • 이벤트 이름은 클릭 같은 구현보다 저장 요청 같은 의도를 표현합니다.
  • 슬롯은 화면 구조 확장이 필요한 지점에 제한적으로 사용합니다.
  • 공통 로직은 composable로 분리하되 전역 상태를 암묵적으로 만들지 않습니다.

v-if와 v-show를 상황에 맞게 선택하기

v-if는 조건이 거짓이면 요소와 하위 컴포넌트를 생성하지 않고, 조건이 바뀔 때 생성과 제거 비용이 듭니다. v-show는 요소를 한 번 생성한 뒤 CSS 표시 여부만 바꿉니다. 거의 바뀌지 않는 무거운 화면은 v-if가, 자주 열고 닫는 작은 메뉴는 v-show가 유리할 수 있습니다. 민감한 정보를 v-show로 숨기는 것은 보안 통제가 아닙니다. 데이터 자체를 권한 없는 클라이언트에 보내지 않아야 합니다.

목록 렌더링의 key를 안정적으로 지정하기

v-for의 key는 Vue가 기존 DOM과 컴포넌트 상태를 올바르게 재사용하도록 돕습니다. 배열 인덱스를 key로 쓰면 중간 항목을 삽입하거나 정렬할 때 입력값과 컴포넌트 상태가 다른 행으로 이동할 수 있습니다. 데이터베이스 ID처럼 항목을 고유하게 식별하고 변하지 않는 값을 사용합니다. key를 매 렌더링마다 무작위로 만들면 모든 요소가 새로 생성돼 성능과 상태 유지가 나빠집니다.

비동기 데이터의 네 가지 상태 표현하기

API 화면은 데이터만 갖는 것이 아니라 대기, 성공, 빈 결과, 오류 상태를 구분해야 합니다. onMounted에서 요청을 시작하고 try/catch/finally로 로딩 종료를 보장합니다. 컴포넌트가 사라진 뒤 응답이 도착할 수 있으므로 AbortController나 HTTP 클라이언트의 취소 기능을 사용합니다. 오류 원문을 그대로 화면에 노출하지 말고 사용자가 재시도하거나 입력을 수정할 수 있는 메시지를 제공합니다.

composable로 재사용 가능한 로직 만들기

useSearchusePagination처럼 use로 시작하는 함수에 반응형 상태와 동작을 묶을 수 있습니다. composable은 컴포넌트에서 분리됐다는 이유만으로 자동 정리되지 않습니다. 이벤트 리스너와 타이머를 등록했다면 onUnmounted에서 해제합니다. 호출할 때마다 독립 상태가 필요한지, 여러 화면이 공유해야 하는지 명확히 해야 합니다.

Vue 실무 적용 순서

  1. 원본 상태와 computed 파생 상태를 구분합니다.
  2. 컴포넌트 간 데이터는 props 아래로, 이벤트 위로 흐르게 합니다.
  3. 목록의 key를 안정적인 업무 식별자로 지정합니다.
  4. 비동기 요청의 로딩·빈 결과·오류·취소를 구현합니다.
  5. 반복 로직을 작은 composable로 분리합니다.
  6. Vue Devtools로 불필요한 업데이트와 상태 흐름을 확인합니다.
  7. 컴포넌트 테스트로 입력과 이벤트 계약을 검증합니다.

백엔드와 연동할 때는 날짜, 큰 정수, NULL 필드의 표현을 API 계약으로 정해야 합니다. 자바스크립트의 안전한 정수 범위를 넘는 ID는 문자열로 전달할 수 있고, 화면 표시용 날짜와 서버 저장 시간대를 분리해야 합니다. TypeScript를 쓰더라도 런타임의 잘못된 API 응답을 자동으로 막지는 못하므로 필요한 곳에서 스키마 검증을 적용합니다.

Vue의 생산성은 짧은 문법보다 예측 가능한 반응성에서 나옵니다. 상태를 최소화하고 파생 값은 computed로 계산하며, 컴포넌트의 입력과 출력을 명확히 하면 기능이 늘어도 수정 범위를 좁힐 수 있습니다.

한 줄 요약: Vue 3 실무에서는 상태 소유권, computed와 watch의 구분, 안정적인 key와 비동기 상태 관리가 유지보수성을 결정합니다.