[Vue] 리렌더링(re-rendering)

Vue는 데이터가 변경되면 화면이 바로 갱신되는 반응형 시스템을 가지고 있다. 컴포넌트 인스턴스에는 watcher라는 것이 있으며, 이 녀석에 의해 어떤 데이터의 수정이 발생하면 다시 렌더링 된다. 아래는 Vue 2 문서에서 어떻게 변경을 감지하는지를 나타내는 그림이다.

vue-reactivity

그런데, 배열객체에서 수정이 발생해도 다시 렌더링이 되지 않는 경우들이 있기 때문에 주의해야 한다.

배열 변경 감지

Vue는 배열의 변경을 감지하기 위해 래핑된 메소드를 제공한다. 이 메소드를 사용해야 Vue가 배열의 변경을 감지하고 화면을 갱신한다.

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

JavaScript 언어적 한계로 인해 Vue는 배열에 대해 다음과 같은 변경 사항을 감지할 수 없다.

  • 인덱스로 배열에 있는 항목을 직접 설정하는 경우
    vm.items[index] = newValue
  • 배열 길이를 수정하는 경우
    vm.items.length = newLength

예시

JavaScript에서는 배열에 요소를 추가 또는 수정할 때, push() 또는 splice()를 주로 사용하는데 arr[2] = 26와 같이 인덱스로 바로 접근할 수도 있다.

테스트 버튼을 누르면 리스트의 첫 번째를 ‘강아지’에서 ‘고양이’로 교체하는 예시이다. splice()를 활용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
  <div id="app">
    <ul>
      <li v-for="(a, idx) in animal" :key="idx">
        {{ a }}
      </li>
    </ul>

    <button @click="test">테스트</button>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      animal: ["강아지", "", "도마뱀"],
    };
  },
  methods: {
    test() {
      this.animal.splice(0, 1, "고양이");
    },
  },
};
</script>

<style></style>
버튼 클릭 전 버튼 클릭 전
버튼 클릭 후 버튼 클릭 후

만약 test()가 인덱스로 바로 접근하는 방식이라면 결과는 아래와 같다. data는 변경됐지만, 화면은 갱신이 되지 않았다.

1
2
3
4
  test() {
    // this.animal.splice(0, 1, "고양이");
    this.animal[0] = '고양이';
  },
현재 data 상태

객체 변경 감지

Vue 2 문서를 보면 Vue는 속성의 추가, 제거를 감지할 수 없다고 한다. 인스턴스 초기화 중에 data 객체의 속성에 대해 getter/setter를 만들고, 이를 통해 속성에 접근해야 감지할 수 있다.

vue-reactivity

예시

user 객체를 정의하고, 테스트버튼을 누르면 address 속성을 추가하려고 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
  <div id="app">
    <ul>
      <li v-for="(value, key, idx) in user" :key="idx">
        {{ key }} : {{ value }}
      </li>
    </ul>

    <button @click="test">테스트</button>
  </div>
</template>


<script>
export default {
  name: "App",
  data() {
    return {
      user: {
        username: "JANG",
        age: 26,
        github: "minho-jang",
      },
    };
  },
  methods: {
    test() {
      this.user.address = "경기도 의왕시";
    },
  },
};
</script>

<style></style>
현재 data 상태

없었던 속성 address를 추가했기 때문에, user.address는 추가되었지만 Vue는 이를 감지하지 못하고 화면을 갱신하지 않는다.

이를 해결하기 위해서 2가지 방법을 사용할 수 있다.

  • Vue.set(object, propertyName, value)
  • 새로운 객체를 재할당

Vue.set(object, propertyName, value)

객체에 반응형 속성을 추가하기 위해 Vue.set()를 사용할 수 있다. 아래와 같이 test()를 변경하고 실행하면 잘 동작하는 것을 볼 수 있다.

1
2
3
  test() {
    this.$set(this.user, 'address', '경기도 의왕시');
  },

새로운 객체를 재할당

새로운 객체를 다시 할당시키는 방법으로도 해결할 수 있다. 이 때, spread 연산자(...)를 활용하면 쉽게 구현할 수 있다.

1
2
3
4
5
6
  test() {
    this.user = {
      ...this.user,
      address: '경기도 의왕시',
    };
  },
버튼 클릭 전 버튼 클릭 전
버튼 클릭 후 버튼 클릭 후

OFF THE RECORD

사실 개발을 하면서 배열을 다루면 splice()sort()와 같은 주로 함수를 사용하기 때문에 배열 변경을 감지하지 못하는 경우는 없었으나, 객체를 다룰 때 많이 헤맸다. 자식 컴포넌트에게 prop로 데이터는 잘 전달된 것 같은데, 화면 갱신이 되지 않아서 watch를 써야하는 건가? Vuex를 써야하는 건가? 싶었다. 이와 비슷한 문제를 겪는다면 Vue가 감지할 수 있도록 데이터를 수정했는지 확인해보자.

댓글 남기기