reactive 객체를 구조 분해했더니 화면이 갱신되지 않은 이유
문제 발생
reactive()로 만든 상태를 구조 분해해서 썼더니, 값은 바뀌는데 화면이 갱신되지 않았습니다.
const state = reactive({ count: 0, keyword: "" });
const { count } = state;
setInterval(() => state.count++, 1000);
// 템플릿의 {{ count }}는 0에서 멈춰 있음전체를 다른 객체로 바꿔 봤더니 이번에는 아예 반응이 끊겼습니다.
let state = reactive({ count: 0 });
state = reactive({ count: 10 }); // 화면은 예전 객체를 계속 봄원인 분석
Vue 공식 문서는 reactive()의 한계를 세 가지로 정리합니다.
- 값 타입 제한 — 객체 타입(객체, 배열,
Map/Set같은 컬렉션)에만 동작합니다.string,number,boolean같은 원시 값은 담을 수 없습니다. - 객체 전체를 교체할 수 없음 — 반응성 추적이 속성 접근을 통해 이루어지므로 항상 같은 참조를 유지해야 합니다. 다른 객체를 대입하면 첫 참조와의 연결이 끊깁니다.
- 구조 분해에 취약함 — 원시 값 속성을 지역 변수로 구조 분해하거나 함수 인자로 넘기면 반응성 연결을 잃습니다.
위 두 증상이 각각 3번과 2번입니다. 그래서 문서는 결론을 명시합니다 — 이런 한계 때문에 ref()를 상태 선언의 기본 API로 권장합니다.
ref()를 쓰면 .value가 번거롭게 느껴지지만, 대신 위 세 가지가 모두 해결됩니다. 다만 언랩(unwrap) 규칙은 알고 있어야 합니다.
- reactive 객체의 속성으로 접근하면 자동 언랩됩니다.
state.count로 쓰고.value가 필요 없습니다. - 배열이나
Map같은 컬렉션의 요소일 때는 언랩되지 않습니다.books[0].value,map.get("count").value처럼 써야 합니다. - 템플릿에서는 최상위 속성일 때만 언랩됩니다.
count는 언랩되지만object.id는 아닙니다.{{ object.id + 1 }}은[object Object]1이 됩니다.
해결 방안
- 기본은
ref()로 선언합니다.
const count = ref(0);
const keyword = ref("");
setInterval(() => count.value++, 1000); // 템플릿의 {{ count }} 갱신됨- 객체 전체를 갈아끼워야 하면
ref에 객체를 담습니다..value를 교체하면 되므로 참조 유지 문제가 사라집니다.
const filters = ref({ keyword: "", page: 1 });
filters.value = { keyword: "vue", page: 1 }; // 정상 동작reactive객체를 꼭 구조 분해해야 하면toRefs()를 거칩니다.
import { toRefs } from "vue";
const { count, keyword } = toRefs(state); // 각각 ref가 되어 연결 유지- 템플릿에서 중첩 속성을 쓸 때는 언랩을 기대하지 않습니다. 계산식 안에서 쓸 값은
computed로 꺼내 두는 편이 안전합니다.
const id = computed(() => object.value.id);- 함수에 넘길 때도 같은 원칙입니다. 원시 값을 넘기면 그 시점의 값일 뿐입니다. 반응성이 필요하면 ref 자체나 getter를 넘깁니다.
useSearch(() => keyword.value); // getter로 넘기기
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.