본문으로 건너뛰기
개발 머꼬
개발 노트Vue
hohyeon.dev18

computed 대신 메서드를 썼더니 정렬이 매 렌더마다 돈 이유

  • #Engineering Note
  • #Vue

문제 발생

목록을 필터링해서 보여주는 계산에 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을 조작하지 마십시오.

이유는 캐싱과 이어져 있습니다. 게터가 언제 몇 번 실행될지는 의존성이 결정하므로, 그 안에 부수 효과를 넣으면 "몇 번 일어날지 모르는 일"이 됩니다.

반환값도 읽기 전용입니다. 문서의 표현대로 계산된 속성의 반환값은 파생된 상태이고 임시 스냅샷입니다. 바꾸지 말고 원본 상태를 갱신해 새 계산이 일어나게 해야 합니다.

해결 방안

  1. 화면에 파생 값을 그릴 때는 computed입니다.
const filteredItems = computed(() =>
  items.value.filter((item) => item.category === category.value),
);
<li v-for="item in filteredItems" :key="item.id">{{ item.name }}</li>
  1. 인자가 필요하면 메서드가 맞습니다. computed는 인자를 받지 않습니다. 인자별 결과가 필요하면 메서드를 쓰거나, 인자를 ref로 올려 computed로 만듭니다.

  2. 부수 효과는 watch로 옮깁니다. "값이 바뀌면 무언가를 한다"는 computed가 아니라 감시자의 일입니다.

watch(filteredItems, (items) => {
  reportViewed(items.length);
});
  1. 반환값을 변경하지 않습니다. 정렬이 필요하면 사본을 만듭니다 — sort()는 원본을 바꾸는 메서드입니다.
const sorted = computed(() => [...items.value].sort(byName));   // ✅
const sorted = computed(() => items.value.sort(byName));        // ❌ 원본까지 바뀜
  1. 양방향이 필요하면 get/set을 씁니다. 입력값을 가공해서 저장해야 하는 경우에 쓰입니다.
const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (value) => {
    [firstName.value, lastName.value] = value.split(" ");
  },
});
  1. 매번 새 객체를 만들면 캐시 효과가 줄어듭니다. 게터가 새 배열·객체를 반환하면 값은 캐시되지만, 그걸 받는 쪽에서 참조 비교를 하면 항상 다르다고 판단합니다. v-forkey를 데이터 고유 값으로 두면 이 부분은 문제가 되지 않습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.