반응형 데이터 처리
데이터 변화에 자동으로 반응하는 Vue 프로그램을 만들어 봅니다.
지금까지 Vue를 공부하면서 다음과 같은 문법을 사용했습니다.
- ref() — 데이터 관리
- {{ }} — 화면 출력
- @click — 이벤트 처리
- v-model — 입력값 연결
- v-if — 조건 처리
- v-for — 반복 처리
이번 장에서는 Vue의 핵심 개념인 반응형 데이터(Reactive Data)를 조금 더 자세히 살펴봅니다.
데이터가 변경되었을 때 그 데이터를 사용하는 화면도 자동으로 변경되는 데이터를 말합니다.
↓
Vue가 변경 감지
↓
관련된 화면 자동 갱신
| 기능 | 역할 |
|---|---|
| ref() | 하나의 값이나 데이터를 반응형으로 관리 |
| reactive() | 객체를 반응형으로 관리 |
| computed() | 기존 데이터를 이용하여 새로운 값을 계산 |
| watch() | 특정 데이터의 변경을 감지하여 작업 실행 |
ref()는 지금까지 계속 사용했던 가장 기본적인 Vue 반응형 기능입니다.
<script setup>
import { ref } from 'vue'
const count = ref(100)
</script>
<template>
<h2>현재 값 : {{ count }}</h2>
</template>
JavaScript 영역에서 ref로 만든 값을 변경할 때는 .value를 사용합니다.
count.value = 200
값이 변경되면 화면에서 사용하고 있는 {{ count }}도 자동으로 변경됩니다.
↓
count.value = 200
↓
화면의 {{ count }}도 200으로 자동 변경
script 영역에서는 count.value를 사용하지만, template 영역에서는 일반적으로 {{ count }}처럼 사용합니다.
ref()는 숫자뿐 아니라 문자열도 반응형 데이터로 관리할 수 있습니다.
<script setup>
import { ref } from 'vue'
const name = ref('홍길동')
function changeName() {
name.value = '김철수'
}
</script>
<template>
<h2>사원 이름</h2>
<p>{{ name }}</p>
<button @click="changeName">
이름 변경
</button>
</template>
실제 프로그램에서는 사원처럼 서로 관련된 여러 데이터를 하나의 묶음으로 관리해야 하는 경우가 많습니다.
ref()를 이용하면 다음과 같이 각각 만들 수 있습니다.
const name = ref('홍길동')
const age = ref(30)
const phone = ref('010-1111-2222')
const department = ref('개발팀')
물론 이렇게 작성해도 되지만, 모두 한 명의 사원에 대한 정보라면 하나의 객체로 묶는 것이 관리하기 편리합니다.
const employee = reactive({
name: '홍길동',
age: 30,
phone: '010-1111-2222',
department: '개발팀'
})
여러 속성을 가진 객체를 하나의 반응형 데이터로 관리할 때 편리하게 사용할 수 있습니다.
<script setup>
import { reactive } from 'vue'
const employee = reactive({
name: '홍길동',
age: 30,
department: '개발팀'
})
</script>
<template>
<h2>사원정보</h2>
<p>이름 : {{ employee.name }}</p>
<p>나이 : {{ employee.age }}</p>
<p>부서 : {{ employee.department }}</p>
</template>
이름 : 홍길동
나이 : 30
부서 : 개발팀
객체의 값을 변경할 때는 속성을 직접 변경합니다.
employee.name = '김철수' employee.age = 35 employee.department = '영업팀'
reactive()로 만든 객체의 속성을 변경할 때는 ref()처럼 .value를 사용하지 않습니다.
name.value = '김철수' // ref() employee.name = '김철수' // reactive()
const name = ref('홍길동')
name.value = '김철수'
const employee = reactive({
name: '홍길동',
age: 30
})
employee.name = '김철수'
| 구분 | ref() | reactive() |
|---|---|---|
| 주 사용 형태 | 단일 값부터 객체까지 | 객체 / 배열 |
| 값 변경 | .value 사용 | 속성 직접 변경 |
| 예 | count.value = 100 | employee.name = '홍길동' |
| 초보자 활용 | 매우 많음 | 객체 관리에 편리 |
count / name / price
→ ref()
여러 속성을 가진 하나의 객체
employee / member / product
→ reactive()
ref()도 객체와 배열을 저장할 수 있으며, 실제 Vue 개발에서도 매우 많이 사용합니다.
reactive()로 만든 객체의 속성도 v-model과 직접 연결할 수 있습니다.
<script setup>
import { reactive } from 'vue'
const employee = reactive({
name: '',
phone: '',
department: '개발팀'
})
</script>
<template>
<h2>사원 등록</h2>
<p>
이름 :
<input v-model="employee.name">
</p>
<p>
전화번호 :
<input v-model="employee.phone">
</p>
<p>
부서 :
<select v-model="employee.department">
<option>개발팀</option>
<option>영업팀</option>
<option>관리팀</option>
</select>
</p>
<hr>
<p>이름 : {{ employee.name }}</p>
<p>전화번호 : {{ employee.phone }}</p>
<p>부서 : {{ employee.department }}</p>
</template>
여러 변수를 각각 만드는 대신 하나의 employee 객체 안에서 관련 데이터를 함께 관리할 수 있습니다.
│
├─ name
├─ phone
└─ department
웹 프로그램에서는 이미 존재하는 데이터를 이용하여 새로운 값을 계산해야 하는 경우가 많습니다.
수량 : 3개
↓
총 금액 : 30,000원
총 금액은 독립적인 데이터라기보다 가격 × 수량으로 계산할 수 있는 값입니다.
다른 반응형 데이터를 이용해 계산된 결과가 필요하다면 computed()를 사용할 수 있습니다.
<script setup>
import { ref, computed } from 'vue'
const price = ref(10000)
const count = ref(2)
const total = computed(() => {
return price.value * count.value
})
</script>
<template>
<h2>상품 주문</h2>
<p>가격 : {{ price }}원</p>
<p>수량 : {{ count }}개</p>
<p>총 금액 : {{ total }}원</p>
</template>
수량 : 2개
총 금액 : 20000원
수량을 3으로 변경해 봅니다.
count.value = 3
count = 3 → total = 30,000
count가 변경되면 Vue가 이를 감지하고 total을 자동으로 다시 계산합니다.
<script setup>
import { ref, computed } from 'vue'
const productName = ref('노트북')
const price = ref(1200000)
const count = ref(1)
const total = computed(() => {
return price.value * count.value
})
function increase() {
count.value++
}
function decrease() {
if (count.value > 1) {
count.value--
}
}
</script>
<template>
<h1>상품 주문</h1>
<p>상품명 : {{ productName }}</p>
<p>
가격 :
{{ price.toLocaleString() }}원
</p>
<p>
수량 :
{{ count }}개
</p>
<button @click="decrease">
-
</button>
<button @click="increase">
+
</button>
<hr>
<h2>
총 금액 :
{{ total.toLocaleString() }}원
</h2>
</template>
가격 : 1,200,000원
수량 : 1개
[-] [+]
총 금액 : 1,200,000원
총 금액 : 2,400,000원
총 금액 : 3,600,000원
↓
computed() 재계산
↓
total 변경
↓
화면 자동 갱신
computed()를 사용하지 않고 계산 결과를 ref()로 관리할 수도 있습니다.
const total = ref(0)
function calculate() {
total.value = price.value * count.value
}
하지만 이 방식에서는 가격이나 수량이 변경될 때마다 calculate()를 직접 호출해야 합니다.
const total = computed(() => {
return price.value * count.value
})
price 변경 ──→ calculate()
count 변경 ──→ calculate()
computed 사용
price ──┐
├──→ total 자동 계산
count ──┘
Vue가 계산에 사용되는 반응형 데이터를 추적하므로 계산 결과를 별도로 계속 관리할 필요가 줄어듭니다.
computed()는 단순한 곱셈뿐 아니라 배열 데이터를 이용한 인원수 계산에도 사용할 수 있습니다.
<script setup>
import { ref, computed } from 'vue'
const employees = ref([
{ id: 1, name: '홍길동', department: '개발팀' },
{ id: 2, name: '김철수', department: '영업팀' },
{ id: 3, name: '이영희', department: '개발팀' }
])
const employeeCount = computed(() => {
return employees.value.length
})
const developerCount = computed(() => {
return employees.value.filter(
emp => emp.department === '개발팀'
).length
})
</script>
<template>
<h2>사원 현황</h2>
<p>
전체 사원 : {{ employeeCount }}명
</p>
<p>
개발팀 : {{ developerCount }}명
</p>
</template>
개발팀 : 2명
employees 배열에 사원을 추가하거나 삭제하면 계산 결과도 자동으로 변경됩니다.
computed()는 기존 데이터를 이용해 새로운 값을 계산할 때 사용합니다.
반면 watch()는 특정 데이터가 변경되었을 때 어떤 작업을 실행하고 싶을 때 사용합니다.
<script setup>
import { ref, watch } from 'vue'
const keyword = ref('')
watch(keyword, (newValue, oldValue) => {
console.log('이전 값 :', oldValue)
console.log('새로운 값 :', newValue)
})
</script>
<template>
<h2>사원 검색</h2>
<input
v-model="keyword"
placeholder="이름 검색"
>
<p>
검색어 : {{ keyword }}
</p>
</template>
사용자가 입력창에 홍을 입력하면 keyword 값이 변경되고, watch()가 이 변화를 감지합니다.
↓
watch() 감지
↓
지정된 함수 실행
수량이 10개를 초과하면 경고 메시지를 표시하는 예제를 만들어 보겠습니다.
<script setup>
import { ref, watch } from 'vue'
const count = ref(1)
watch(count, (newCount) => {
if (newCount > 10) {
alert('10개를 초과했습니다.')
}
})
function increase() {
count.value++
}
</script>
<template>
<h2>수량 : {{ count }}</h2>
<button @click="increase">
증가
</button>
</template>
↓
watch() 실행
↓
조건 확인
↓
필요한 작업 실행
특정 값이 변경되었을 때 로그 기록, 경고 표시, 추가적인 처리 등을 실행해야 할 때 활용할 수 있습니다.
처음 Vue를 배우면 computed()와 watch()가 비슷해 보일 수 있습니다. 다음과 같이 역할을 구분하면 이해하기 쉽습니다.
| 기능 | 목적 | 예 |
|---|---|---|
| computed() | 새로운 값 계산 | 총 금액, 인원수 |
| watch() | 변경 감지 후 작업 실행 | 로그, 경고, 추가 처리 |
→ computed()
↓
10개 초과인지 확인
↓
경고창 표시
→ watch()
computed() → "결과값이 무엇인가?"
watch() → "값이 바뀌었을 때 무엇을 할 것인가?"
<script setup>
import {
ref,
reactive,
computed,
watch
} from 'vue'
const product = reactive({
name: '노트북',
price: 1200000
})
const count = ref(1)
const total = computed(() => {
return product.price * count.value
})
function increase() {
count.value++
}
function decrease() {
if (count.value > 1) {
count.value--
}
}
watch(count, (newCount) => {
if (newCount >= 5) {
console.log('대량 주문입니다.')
}
})
</script>
<template>
<h1>상품 주문 관리</h1>
<p>
상품명 : {{ product.name }}
</p>
<p>
가격 :
{{ product.price.toLocaleString() }}원
</p>
<p>
수량 : {{ count }}개
</p>
<button @click="decrease">
감소
</button>
<button @click="increase">
증가
</button>
<hr>
<h2>
총 금액 :
{{ total.toLocaleString() }}원
</h2>
</template>
├──→ computed() → total → 화면
count ────────────────┘
│
├──→ 화면
└──→ watch() → 변경 감지
데이터 사이의 관계를 만들어 두면 데이터가 변경될 때 필요한 계산 결과와 화면이 자동으로 반응합니다.
마지막으로 reactive()를 실제 사원관리 프로그램과 비슷한 형태로 사용해 봅니다.
<script setup>
import { reactive } from 'vue'
const employee = reactive({
name: '홍길동',
phone: '010-1111-2222',
department: '개발팀'
})
function changeEmployee() {
employee.name = '김철수'
employee.phone = '010-3333-4444'
employee.department = '영업팀'
}
function clearEmployee() {
employee.name = ''
employee.phone = ''
employee.department = ''
}
</script>
<template>
<h1>사원정보</h1>
<p>
이름 :
<input v-model="employee.name">
</p>
<p>
전화번호 :
<input v-model="employee.phone">
</p>
<p>
부서 :
<input v-model="employee.department">
</p>
<button @click="changeEmployee">
정보 변경
</button>
<button @click="clearEmployee">
초기화
</button>
<hr>
<h2>현재 정보</h2>
<p>이름 : {{ employee.name }}</p>
<p>전화번호 : {{ employee.phone }}</p>
<p>부서 : {{ employee.department }}</p>
</template>
정보 변경 버튼을 누르면 changeEmployee() 함수에서 employee 객체의 속성을 변경합니다.
employee.name = '김철수' employee.phone = '010-3333-4444' employee.department = '영업팀'
전화번호 : 010-3333-4444
부서 : 영업팀
데이터가 변경되면 v-model로 연결된 입력창과 {{ }}로 출력하는 현재 정보 영역이 함께 자동으로 변경됩니다.
↓
Vue가 변경 감지
↓
입력창 자동 변경
+
출력 화면 자동 변경
Vue에서는 화면의 HTML을 직접 찾아 수정하는 대신 데이터를 변경합니다.
그러면 Vue가 그 데이터를 사용하는 화면을 자동으로 갱신합니다. 이것이 Vue에서 데이터를 중심으로 프로그램을 관리하는 기본 방식입니다.
- 반응형 데이터는 데이터가 변경될 때 연결된 화면도 자동으로 변경되는 데이터입니다.
- ref()는 숫자, 문자열 등 다양한 데이터를 반응형으로 관리할 수 있습니다.
- script 영역에서 ref()의 값을 변경할 때는 .value를 사용합니다.
- reactive()는 여러 속성을 가진 객체를 하나의 반응형 데이터로 관리할 때 편리합니다.
- reactive() 객체의 속성을 변경할 때는 .value를 사용하지 않고 속성을 직접 변경합니다.
- reactive() 객체의 속성도 v-model과 연결할 수 있습니다.
- computed()는 기존 반응형 데이터를 이용하여 새로운 값을 계산할 때 사용합니다.
- computed()가 사용하는 데이터가 변경되면 계산 결과도 자동으로 다시 계산됩니다.
- watch()는 특정 데이터의 변경을 감지한 뒤 필요한 작업을 실행할 때 사용합니다.
- computed()는 "결과값이 무엇인가?", watch()는 "값이 바뀌었을 때 무엇을 할 것인가?"로 구분하면 이해하기 쉽습니다.
- Vue에서는 화면을 직접 수정하기보다 데이터를 관리하고 데이터 사이의 관계를 만드는 방식으로 프로그램을 구성합니다.