👨🏻💻
Day 54 Vue 반응형 API
February 13, 2025
반응형 API (Reactivity API)
- 반응형 API (Reactivity API)는 데이터의 변경을 자동으로 감지하고, UI를 최신 상태로 유지하는 API이다.
- 기본적으로 Vue는 데이터가 변경되면 자동으로 DOM을 업데이트하는데, 이를 가능하게 하는 핵심 개념이 반응형 시스템(Reactivity System)이다.
- React의 Hooks와 유사한 개념으로 useState를 ref, reactive로 useEffect는 watch와 watchEffect가 대응
ref
- ref()는 기본 데이터 타입(Number, String, Boolean, Object, Array 등)을 반응형으로 만드는 API 이다.
- 기본형을 감싸서(Wrapping)하여 관리함에 따라 .value 속성을 통해 값을 읽고 수정이 가능하다.
- 객체를 ref로 감싸면 내부적으로 reactive()와 유사하게 동작하며, reactive 다르게 ref로는 null로 초기화 가능하고 객체로 할당하는 경우 reactive를 통해 깊숙이(deeply) 반응함으로 속성값 변경도 감지 된다.
- 간혹, deep 하게 감지 되지 않는 경우도 존재한다. watch 결합과 같은 경우, 이때는 deep 옵션 활용 필수다
기본형 예제
<script setup>
import { ref } from 'vue';
const count = ref(0);
const message = ref('');
const number = ref(3);
const isChecked = ref(false);
const increment = () => {
count.value++;
};
</script>
<template>
카운트 : {{ count }} <br />
<button @click="increment">증가</button> <br /><br />
입력한 메시지 :{{ message }} <br />
<input type="text" name="" id="" v-model="message" placeholder="메세지를 입력바랍니다" /> <br />
<br />
입력한 숫자 : {{ number * 10 }} <br />
<input type="text" name="" id="" v-model="number" placeholder="숫자를 입력하세요" /> <br />
<br />
체크 상태 : {{ isChecked }} <br />
<input type="checkbox" name="" id="" v-model="isChecked" /> <br />
<br />
</template>객체 예제
<script setup>
import { onMounted, ref } from 'vue';
// 객체 선언
const user = ref(null);
// 배열 선언
const languages = ref(['C', 'Java', 'JavaScript']);
onMounted(() => {
// axios로 데이터 받아오는 곳
setTimeout(() => {
user.value = { name: '홍길동', age: '25', favoriteLanguage: 'Java' };
}, 3000);
});
</script>
<template>
<template v-if="user">
<h3>사용자 정보 입력</h3>
이름 : <input type="text" v-model="user.name" /> <br />
나이 : <input type="number" v-model="user.age" /> <br />
좋아하는 언어 :
<select v-model="user.favoriteLanguage">
<option v-for="item in languages" :key="item" :value="item">{{ item }}</option>
</select>
<h3>입력한 정보</h3>
이름 : {{ user.name }} <br />
나이 : {{ user.age }} <br />
좋아하는 언어 : {{ user.favoriteLanguage }} <br />
</template>
<template v-else>
<h3>로딩중...</h3>
</template>
</template>- onMounted에 의해 Dom이 마운트 된 후에 setTimeout이 동작된다.
- 처음 렌더링 시에는 로딩중으로 나오게 된다.
- setTimeout 함수가 3초 지난후에는 user의 값이 바뀌어 재렌더링이 된다.
reactive
- reactive()는 객체(Object)와 배열(Array) 를 반응형 상태로 만드는 API이다.
- ref와 달리 Wrapping 하지 않고 반응형 프록시(proxy, 변경을 감지하는 JS 패턴)를 활용, .value를 사용하지 않고 속성에 직접 접근이 가능하다.
- ref()와 달리 기본 타입은 사용할 수 없고, 객체나 배열만 가능하며, null로 초기화 될 수 없다.
- Deep(깊은 복사) 동작함으로 ref에서 객체로 활용 할 때 이슈가 있으면 reactive를 활용 권장한다고 한다.
객체 예제
<script setup>
import { onMounted, reactive, ref } from 'vue';
const isLoading = ref(true);
const user = reactive({ name: '홍길동', age: 25, favoriteLanguage: 'JavaScript' });
const languages = reactive(['C', 'Java', 'JavaScript']);
onMounted(() => {
setTimeout(() => {
const newUser = { name: '홍길동', age: 25, favoriteLanguage: 'Java' };
// 속성 직접 접근
user.name = newUser.name;
user.age = newUser.age;
user.favoriteLanguage = newUser.favoriteLanguage;
isLoading.value = false;
}, 3000);
});
</script>
<template>
<h3>사용자 정보 입력</h3>
이름 : <input v-model="user.name" type="text" /> <br />
나이 : <input v-model="user.age" type="number" /> <br />
좋아하는 언어:
<select v-model="user.favoriteLanguage" class="input">
<option v-for="lang in languages" :key="lang" :value="lang">
{{ lang }}
</option>
</select>
<br />
<h3>입력한 정보</h3>
이름: {{ user.name }}<br />
나이: {{ user.age }}<br />
좋아하는 언어: {{ user.favoriteLanguage }}<br />
</template>computed( ) - 반응형 재계산
- computed()는 기존 데이터(state)를 기반으로 새로운 값을 자동 계산하는 API이다.
- 종속된 값이 변경되면 자동으로 재계산 되며, 읽기 전용으로 활용한다. (기본적으로 읽기 전용이지만, set을 지원하기도 한다.)
- react의 useMemo와 유사한 방법으로 함수 내부의 값들이 변경되면 자동으로 계산되는 기능이라고 한다.
기본형 예제
<script setup>
import { computed, ref } from 'vue';
const price = ref(1000);
const quantity = ref(2);
// 자동으로 계산되는 속성
const totalPrice = computed(() => price.value * quantity.value);
</script>
<template>
<h3>물품 가격</h3>
가격 : <input type="text" name="" id="" v-model="price" /> 원 <br />
수량 : <input type="text" name="" id="" v-model="quantity" /> 개 <br />
<b>총 가격 : {{ totalPrice }} 원</b>
</template>get / setter 예제
<script setup>
import { computed, ref } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed({
// getter
get() {
return firstName.value + ` ` + lastName.value;
},
//setter
set(newValue) {
if (newValue.includes(' ')) {
[firstName.value, lastName.value] = newValue.split(' ');
}
},
});
</script>
<template>
<div>
firstName : <input type="text" name="" id="" v-model="firstName" /> <br />
lastName : <input type="text" name="" id="" v-model="lastName" /> <br />
fullName: <input type="text" name="" id="" v-model="fullName" /> <br />
fullName: {{ fullName }} <br />
</div>
</template>-
Getter firstName과 lastName을 공백(” “)으로 연결하여 fullName을 반환한다. (예: “John Doe”)
-
Setter 새로운 fullName 값이 입력되었을 때, 공백이 포함된 경우 split(’ ‘)을 사용하여 firstName과 lastName을 각각 나눈다. (예: “John Doe” → firstName = “John”, lastName = “Doe”)
watch() - 데이터 변경 감지
- watch( )는 특정 데이터의 변화를 감지하는 API
- 보통 변경을 감지하고 특정 로직 (예: API 호출, 상태 업데이트, 콘솔 로그, 알림 등) 을 실행한다.
- computed( )는 결과값을 반환하지만, watch()는 반응형 데이터를 변경했을 때 특정 동작을 실행 한다.
- ref 객체나 배열 조합시에는 반드시 { deep: true } 옵션이 필수다.
watch - 기본형 예제(데이터 1개 감시)
<script setup>
import { ref, watch } from 'vue';
const count = ref(0);
const logs = ref([]);
// 기본
// watch(count, () => {
// logs.value.push(count.value)
// })
// 신규값만 가져오는 방법
// watch(count, (newValue) => {
// logs.value.push(newValue)
// })
// 3. new + old 값 가져오기 => 현재 값, 이전 값
watch(count, (newValue, oldValue) => {
console.log(`count 변경: ${oldValue} → ${newValue}`);
logs.value.push(`count 변경: ${oldValue} → ${newValue}`);
});
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
</script>
<template>
<div>
count : {{ count }} <br />
<button @click="increment">증가</button>
<button @click="decrement">감소</button>
<div v-for="item in logs" :key="item">{{ item }}</div>
</div>
</template>watch - 기본형 예제(데이터 2개 이상 감시)
<script setup>
import { ref, watch } from 'vue';
const price = ref(1000);
const quantity = ref(2);
const totalPrice = ref(price.value * quantity.value);
const logs = ref([]);
// 자동으로 계산되는 속성
// const totalPrice = computed(() => price.value * quantity.value)
// watch 처리
// 1. 여러개 기본형
watch([price, quantity], () => {
totalPrice.value = price.value * quantity.value;
logs.value.push(`${price.value}, ${quantity.value}`);
});
</script>
<template>
<h3>물품 가격</h3>
가격 : <input type="text" name="" id="" v-model="price" /> 원 <br />
수량 : <input type="text" name="" id="" v-model="quantity" /> 개 <br />
<b>총 가격 : {{ totalPrice }} 원</b>
<div v-for="item in logs" :key="item">{{ item }}</div>
</template>- price, quantity 둘 중에 하나의 값이라도 변경되면 해당 로직을 실행한다.
watchEffect() - 자동 감지 및 실행
- watchEffect()는 내부에서 사용된 모든 반응형 상태를 자동으로 감지하는 API이다.
- watch()와 달리 어떤 변수를 감시할지 명시할 필요 없으나 모든 변수의 변화를 감지하여 동작한다.
- 이전 값에 대한 모니터링이 불가능하다
watchEffect - 기본형 예제
<script setup>
import { ref, watch, watchEffect } from 'vue';
const count = ref(0);
const logs = ref([]);
watchEffect(() => {
logs.value.push(`count 변경 : ${count.value}`);
});
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
</script>
<template>
<p>카운트: {{ count }}</p>
<button @click="increment">증가</button>
<button @click="decrement">감소</button>
<div v-for="item in logs" v-bind:key="item">
{{ item }}
</div>
</template>