Vue 속도 향상을 위한 방법

clean·2022년 11월 28일
0

Vue 속도 향상을 위한 방법

아래 블로그에 작성된 내용을 옮김
https://rutgo-letsgo.tistory.com/286

목록 렌더링 시 주의 사항

HTML Element 대신 컴포넌트 사용하기 (목록이 업데이트될 수 있는 경우)

  • 필요할 때만 렌더링되도록 주의해야함
  • div 로 목록을 그렸을 때
    • array 에 목록 추가/수정/삭제 시 모든 div 가 업데이트 됨
  • 컴포넌트로 목록을 그렸을 때
    • 컴포넌트는 반응형으로 변화가 있을 때만 업데이트 됨
  • 예시 코드
    • 재 렌더링이 되었는지 여부를 new Date() 를 통해 확인
  <template>
  <div class="hello">
    <button @click="action.addItem">addItem</button>
    <button @click="action.deleteItem">deleteItem</button>
    <button @click="action.updateFirstItem">updateFirstItem</button>
    <div class="list_box">
      <div>Div</div>
      <div v-for="item in arr" :key="item">
        {{ item }}
        {{ new Date() }}
      </div>
    </div>
    <div class="list_box">
      <div>ItemNumber Component</div>
      <item-number v-for="item in arr" :key="item" :value="item"></item-number>
    </div>
  </div>
</template>
import { defineComponent, reactive, toRefs } from "vue";
import ItemNumber from "@/components/ItemNumber.vue";

export default defineComponent({
  name: "HelloWorld",
  components: { ItemNumber },
  props: {
    msg: String,
  },
  setup() {
    const state = reactive({
      arr: [1, 2, 3],
    });

    const action = {
      addItem() {
        const { length } = state.arr;
        state.arr.push(length);
      },
      deleteItem() {
        state.arr.splice(0, 1);
      },
      updateFirstItem() {
        state.arr[0] = 500;
      },
    };

    return {
      ...toRefs(state),
      action,
    };
  },
});
.list_box {
  padding: 20px;
}
<!-- ItemNumber.vue -->
<template>
  <div>{{ value }} {{ new Date() }} {{ userStatus }}</div>
</template>

<script>
export default {
  name: "ItemNumber",
  props: {
    value: Number,
    userStatus: String,
  },
};
</script>
  • 실행 화면
    • Div 로 되어있는 목록은 배열에 add/delete/update 할 때 마다 모든 요소들이 재렌더링 되고있다.
    • 반면 Component 로 되어있는 목록은 add/delete 될 때 (추가되는 자기 자신이 아닌 한) 렌더링 되지 않고 수정될때도 수정되는 요소만 재렌더링 되고 있는 모습을 확인할 수 있다.

중복 렌더링 방지하기

  • 렌더링 의존성에 대해 파악하여 중복으로 렌더링이 되는 것을 방지해야 함
  • 위 예시 코드와 동일할 때 만약 ItemNumber 컴포넌트가 아래와 같이 Store로 부터 User 객체의 Status 값을 뿌려주는 부분이 있을 때
    - Store 에 User 가 재할당되면 User Status의 값이 동일하더라도 모든 ItemNumber 컴포넌트가 재 렌더링 된다.
<!-- ItemNumber.vue -->
<template>
  <div>{{ value }} {{ new Date() }} {{ status }}</div>
</template>

<script>
import { computed } from "vue";
import { useStore } from "vuex";

export default {
  name: "ItemNumber",
  props: {
    value: Number,
  },
  setup() {
    const store = useStore();
    const status = computed(() => store.state.user.status);
    return { status };
  },
};
</script>
<!-- HelloWorld.vue -->
<script>
export default {
  // ... 생략
  setup() {
    const action = {
       // ... 생략
       updateUser() {
        // user 값 업데이트
       	store.commit("setUser", {
          status: "fail",
        });
      }
    }
    return { status };
  },
};
</script>
// store/index.ts
import { createStore } from "vuex";

export default createStore({
  state: {
    user: {
      status: "success",
    },
  },
  getters: {},
  mutations: {
    setUser(state, value) {
      state.user = value;
    },
  },
  actions: {},
  modules: {},
});

  • user 값이 재할당 될 때마다 (업데이트될 user의 status 값은 'fail' 로 동일) 각각의 ItemNumber 가 재 렌더링 되고 있음을 확인할 수 있다.
    • 예시에서는 ItemNumber 의 부모 컴포넌트에서 User 값을 업데이트했지만 Store 에 있는 값이므로 실제 프로젝트에서는 어디에서든 변경가능성이 있음
  • 위와 같은 상황을 방지하기 위해서 제일 간단하게는 역시 Props 를 이용할 수 있다. 실제 Status 값이 변경될 때만 재렌더링되도록 아래와 같이 수정한다.
<!-- HelloWorld.vue -->
<template>
  <!-- 생략.. -->
      <!-- user-status props 추가 -->
      <item-number
        v-for="item in arr"
        :key="item"
        :value="item"
        :user-status="userStatus"
      ></item-number>
  <!-- 생략.. -->
</template>

<script lang="ts">
export default defineComponent({
  name: "HelloWorld",
  components: { ItemNumber },
  props: {
    msg: String,
  },
  setup() {
    // .. 생략
    // 추가
    const userStatus = computed(() => store.state.user.status);
    return {
      ...toRefs(state),
      userStatus, // 추가
      action,
    };
  },
</script>
<!-- ItemNumber.vue -->
<template>
  <div>{{ value }} {{ new Date() }} {{ userStatus }}</div>
</template>

<script>
export default {
  name: "ItemNumber",
  props: {
    value: Number,
    userStatus: String, // props 이용하여 출력하도록 수정
  },
};
</script>
  • User 가 재할당 되어도 Status 가 동일하다면 ItemNumber 컴포넌트들은 재렌더링 되지 않는다.

이벤트 핸들링 최적화

  • mouseover 와 scroll 같이 짧은 시간 안에 여러번 일어날 수 있는 이벤트에 대해서는 debounce 함수를 이용해 이벤트 함수를 처리하는 횟수를 제한해야 한다.

v-once Directive 활용

  • 한번만 mounted 된 후 변경될 일이 없는 요소라면 v-once directive를 활용해야 한다.
    • v-once 로 선언된 경우 reactive 한 data 가 변경되어도 재렌더링 되지 않는다.
    • 위 예제에서 사용한 코드의 item-number 에 v-once directive 를 넣어보면
      <template>
      <div class="hello">
        <button @click="action.addItem">addItem</button>
        <button @click="action.updateUser">updateUser</button>
        <div class="list_box">
          <div>ItemNumber Component</div>
          <item-number
            v-once
            v-for="item in arr"
            :key="item"
            :value="item"
            :user-status="userStatus"
          ></item-number>
        </div>
      </div>
      </template>
    • userStatus 의 값이 success=>fail 로 바껴도, arr 에 요소가 추가되어도 변경된 부분을 다시 그리지 않는다.

0개의 댓글