computed 대신 메서드를 썼더니 정렬이 매 렌더마다 돈 이유
문제 발생
목록을 필터링해서 보여주는 계산에 computed 대신 메서드를 썼더니, 스크롤할 때마다 정렬 함수가 수십 번씩 실행됐습니다.
<template>
<li v-for="item in filteredItems()" :key="item.id">{{ item.name }}</li>
</template>반대로 computed 안에서 조회 수를 올렸더니, 값이 갱신되는 시점이 예측 불가능해졌습니다.
const summary = computed(() => {
viewCount.value++; // ❌
return items.value.length + "건";
});원인 분석
메서드는 캐시되지 않습니다. 공식 문서의 설명대로 계산된 속성은 반응형 의존성을 기준으로 캐시되어, 의존하는 값이 바뀌지 않는 한 여러 번 접근해도 게터를 다시 실행하지 않고 이전 결과를 즉시 돌려줍니다. 반면 메서드 호출은 리렌더링이 일어날 때마다 항상 함수를 실행합니다.
게터는 순수해야 합니다. 문서가 이것도 굵게 못박습니다 — 계산된 게터 함수는 순수한 계산만 수행하고 부수 효과가 없어야 합니다. 게터 안에서 다른 상태를 변경하거나, 비동기 요청을 보내거나, DOM을 조작하지 마십시오.
이유는 캐싱과 이어져 있습니다. 게터가 언제 몇 번 실행될지는 의존성이 결정하므로, 그 안에 부수 효과를 넣으면 "몇 번 일어날지 모르는 일"이 됩니다.
반환값도 읽기 전용입니다. 문서의 표현대로 계산된 속성의 반환값은 파생된 상태이고 임시 스냅샷입니다. 바꾸지 말고 원본 상태를 갱신해 새 계산이 일어나게 해야 합니다.
해결 방안
- 화면에 파생 값을 그릴 때는
computed입니다.
const filteredItems = computed(() =>
items.value.filter((item) => item.category === category.value),
);<li v-for="item in filteredItems" :key="item.id">{{ item.name }}</li>-
인자가 필요하면 메서드가 맞습니다.
computed는 인자를 받지 않습니다. 인자별 결과가 필요하면 메서드를 쓰거나, 인자를 ref로 올려computed로 만듭니다. -
부수 효과는
watch로 옮깁니다. "값이 바뀌면 무언가를 한다"는computed가 아니라 감시자의 일입니다.
watch(filteredItems, (items) => {
reportViewed(items.length);
});- 반환값을 변경하지 않습니다. 정렬이 필요하면 사본을 만듭니다 —
sort()는 원본을 바꾸는 메서드입니다.
const sorted = computed(() => [...items.value].sort(byName)); // ✅
const sorted = computed(() => items.value.sort(byName)); // ❌ 원본까지 바뀜- 양방향이 필요하면
get/set을 씁니다. 입력값을 가공해서 저장해야 하는 경우에 쓰입니다.
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (value) => {
[firstName.value, lastName.value] = value.split(" ");
},
});- 매번 새 객체를 만들면 캐시 효과가 줄어듭니다. 게터가 새 배열·객체를 반환하면 값은 캐시되지만, 그걸 받는 쪽에서 참조 비교를 하면 항상 다르다고 판단합니다.
v-for의key를 데이터 고유 값으로 두면 이 부분은 문제가 되지 않습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.