
computed() : 계산된 속성을 정의하는 함수
반환되는 값은 computed ref이며 일반 refs와 유사하게 계산된 결과를 .value로 참조할 수 있음
의존된 반응형 데이터를 자동으로 추적한다
의존하는 데이터가 변경될 때만 재평가 따라서 변경될 때만 업데이트 된다!
⚠️ computed의 반환 값은 변경하지 말 것 (일종의 snapshot, 읽기 전용)
⚠️ reactive 타입에 원본 배열을 조작하는 함수를 붙여준다면 무한루프 발생할 수 있다!
spread를 사용해서 값들을 뿌려주고 조작해준다
import { ref, computed } from 'vue'
const todos = ref([
{ text: '이메일 확인하기', done: true },
{ text: '미팅 준비하기', done: false }
])
// 완료되지 않은 할일 개수에 따른 메시지
const todoMessage = computed(() => {
const remaining = todos.value.filter(t => !t.done).length
return remaining === 0 ? '모든 할일 완료!' : `${remaining}개 할일 남음`
})
// 안전한 배열 조작 (무한 루프 방지)
const reversedTodos = computed(() => [...todos.value].reverse())
computed 속성은 의존된 반응형 데이터를 기반으로 캐시(cached) 된다
의존하는 데이터가 변경된 경우에만 재평가 됨
반면 method 호출은 다시 렌더링이 발생할 때마다 항상 함수
// computed - 캐싱됨 (의존성 변경 시에만 재계산)
const cachedValue = computed(() => {
console.log('computed 실행')
return count.value * 2
})
// method - 매번 호출될 때마다 실행됨
function nonCachedValue() {
console.log('method 실행')
return count.value * 2
}
computed 속성은 의존성이 변경될 때만 결과를 다시 계산하고, 그렇지 않으면 이전에 계산된 결과를 캐시에서 반환합니다. 이로 인해 동일한 계산을 반복적으로 수행하는 것을 방지하여 성능을 최적화합니다.
directive를 사용해서 블록을 나타낼 수 있다!
directive이기 때문에 단일 요소에만 연결 가능하다
<div v-if="isLoggedIn">환영합니다!</div>
<div v-else-if="isLoading">로딩 중...</div>
<div v-else>로그인이 필요합니다</div>
<!-- 여러 요소에 적용 시 template 사용 -->
<template v-if="showContent">
<h1>제목</h1>
<p>내용</p>
</template>
렌더링 vs 가시성
v-if 는 렌더링을 하냐 안하냐, v-show는 렌더링이 된 상태로 css의 display 속성을 변경
그러니 v-if는 초기 조건은 적지만 자주 전환하면 좋지 않다 그럴땐 v-show 를 쓰자!
<!-- v-if: 조건이 false일 때 DOM에서 제거됨 -->
<div v-if="isVisible">v-if로 조건부 렌더링</div>
<!-- v-show: display: none으로 숨겨짐 -->
<div v-show="isVisible">v-show로 조건부 표시</div>
소스 데이터를 기반으로 요소 또는 템플릿 블록을 여러 번 렌더링
alias in expression 형식의 특수 구문을 사용하여 반복되는 현재 요소에 대한 별칭을 제공한다
value, key, index 순서! (배열은 key가 없다)
<!-- 기본 배열 순회 -->
<ul>
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
</ul>
<!-- 인덱스 포함 -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index + 1 }}. {{ item.text }}
</li>
</ul>
<!-- 객체 속성 순회 -->
<ul>
<li v-for="(value, key, index) in userObject" :key="key">
{{ key }}: {{ value }}
</li>
</ul>
template을 활용해서 하나 이상의 요소에 대해 반복 렌더링이 가능하다!
(중첩도 가능!)
<template v-for="item in items" :key="item.id">
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
</template>
<!-- 중첩 v-for -->
<ul>
<li v-for="category in categories" :key="category.id">
{{ category.name }}
<ul>
<li v-for="product in category.products" :key="product.id">
{{ product.name }}
</li>
</ul>
</li>
</ul>
내부 컴포넌트의 상태를 일관되게 유지
key는 반드시 각 요소에 대한 고유한 값을 나타낼 수 있는 식별자여야 함
[주의] 배열의 인덱스를 v-for의 key로 사용하지 말 것
<!-- 권장: 고유 ID를 key로 사용 -->
<div v-for="item in items" :key="item.id">
{{ item.text }}
</div>
<!-- 권장하지 않음: 인덱스를 key로 사용 -->
<div v-for="(item, index) in items" :key="index">
{{ item.text }}
</div>
<!-- 권장하지 않음: 동일 요소에 v-for와 v-if 함께 사용 -->
<li v-for="item in items" v-if="item.isActive" :key="item.id">
{{ item.name }}
</li>
<!-- 해결법 1: computed 속성 사용 -->
<script setup>
import { computed } from 'vue'
const activeItems = computed(() => items.filter(item => item.isActive))
</script>
<li v-for="item in activeItems" :key="item.id">{{ item.name }}</li>
<!-- 해결법 2: v-if를 상위 요소로 이동 -->
<template v-if="hasActiveItems">
<li v-for="item in items" :key="item.id" v-if="item.isActive">
{{ item.name }}
</li>
</template>
반응형 데이터를 감시하기!
variable : 감시하는 변수
newValue : 감시하는 변수가 변화된 값
oldValue : 콜백 함수의 두번째 인자
import { ref, watch } from 'vue'
const searchQuery = ref('')
const results = ref([])
// 기본 watch 사용
watch(searchQuery, (newValue, oldValue) => {
console.log(`검색어가 '${oldValue}'에서 '${newValue}'로 변경됨`)
fetchResults(newValue)
})
// 즉시 실행 (immediate)
watch(searchQuery, (newValue) => {
fetchResults(newValue)
}, { immediate: true })
// 깊은 감시 (deep)
const user = ref({ name: '홍길동', settings: { theme: 'dark' } })
watch(user, (newValue) => {
saveUserSettings(newValue)
}, { deep: true })
둘다 캐시를 이용해서 변화를 감지하고 처리한다
차이점으로는 Computed는 return으로 계산된 값을 반환하고, 연산쪽에 치중되어있고 Watch는 변화를 감시하고 그에 이어서 작업을 수행하는 비동기 API 요청에 특화되어있다.
// Computed: 값 계산 및 반환에 적합
const fullName = computed(() => firstName.value + ' ' + lastName.value)
// Watch: 값 변경 시 사이드 이펙트 수행에 적합
watch(searchQuery, async (newQuery) => {
if (newQuery.length > 2) {
isLoading.value = true
results.value = await api.search(newQuery)
isLoading.value = false
} else {
results.value = []
}
})
Watch가 매우 중요하니 공식문서를 한번 쭉 읽고 따로 포스팅 하자!

1. Vue 컴포넌트 인스턴스가 초기 렌더링 및 DOM 요소 생성이 완료된 후 특정 로직을 수행하기
2. 반응형 데이터의 변경으로 인해 컴포넌트의 DOM이 업데이트된 후 특정 로직을 수행하기
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
const count = ref(0)
// 컴포넌트가 마운트된 후 실행
onMounted(() => {
console.log('컴포넌트가 마운트되었습니다')
fetchInitialData()
})
// 컴포넌트가 업데이트된 후 실행
onUpdated(() => {
console.log('컴포넌트가 업데이트되었습니다')
})
// 컴포넌트가 제거되기 전 실행
onUnmounted(() => {
console.log('컴포넌트가 제거되었습니다')
clearInterval(timer)
})
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onActivated,
onDeactivated
} from 'vue'
// 컴포넌트가 마운트되기 전
onBeforeMount(() => {
console.log('DOM에 마운트되기 전')
// DOM이 생성되기 전에 호출됨
})
// 컴포넌트가 마운트된 후
onMounted(() => {
console.log('DOM에 마운트됨')
// DOM에 접근 가능, API 요청, 타이머 설정 등 수행
fetchData()
initializeThirdPartyLibrary()
})
// 컴포넌트가 업데이트되기 전
onBeforeUpdate(() => {
console.log('컴포넌트 업데이트 전')
// 업데이트 전 상태에 접근 가능
})
// 컴포넌트가 업데이트된 후
onUpdated(() => {
console.log('컴포넌트 업데이트 후')
// DOM이 업데이트된 후 추가 작업 수행
})
// 컴포넌트가 언마운트되기 전
onBeforeUnmount(() => {
console.log('컴포넌트 언마운트 전')
// 정리 작업 시작
})
// 컴포넌트가 언마운트된 후
onUnmounted(() => {
console.log('컴포넌트 언마운트 후')
// 이벤트 리스너 제거, 타이머 정리 등 수행
clearInterval(timer)
removeEventListeners()
})
// 에러 발생 시 처리
onErrorCaptured((err, instance, info) => {
console.error('에러 발생:', err)
// 에러 처리 로직
reportError(err)
return false // 에러가 더 전파되지 않도록 방지
})
// keep-alive 컴포넌트에서 활성화될 때
onActivated(() => {
console.log('컴포넌트 활성화됨')
// keep-alive 컴포넌트가 다시 활성화될 때 실행
})
// keep-alive 컴포넌트에서 비활성화될 때
onDeactivated(() => {
console.log('컴포넌트 비활성화됨')
// keep-alive 컴포넌트가 캐시될 때 실행
})