vue 3.5 버전 업데이트 정리

Dae-Hee·2024년 9월 24일
post-thumbnail
3.5에서 Vue의 반응성 시스템은 또 다른 주요 리팩터링을 거쳤으며, 
더 나은 성능과 크게 향상된 메모리 사용량( -56% )을 달성했지만 동작은 변경되지 않았습니다. 
이 리팩터링은 또한 SSR 중에 계산된 값이 중단되어 발생하는 오래된 계산된 값과 메모리 문제를 해결합니다.

또한 3.5에서는 대규모의 반응형 배열에 대한 반응성 추적을 최적화하여 
이러한 작업을 어떤 경우에는 최대 10배까지 빠르게 수행할 수 있습니다.

https://blog.vuejs.org/posts/vue-3-5

어떤식으로 메모리 사용량과 빠르게 만들었을까?
PR 세부 정보 확인 해보고 정리해보자!


성능 관련 최적화

대규모 반응형 배열에 대한 반응성 추적을 최적화(최대 10배까지 빠르게 수행)

ARRAY_ITERATE_KEY 도입

ARRAY_ITERATE_KEY는 Vue.js의 반응성 시스템에서 배열 전체에 대한 종속성을 추적하기 위해 도입된 키
기존에는 배열의 각 요소나 인덱스를 개별적으로 추적해야 했지만, 이 키를 사용하면 배열 전체의 변경 사항을 한 번에 감지할 수 있다.

  • 배경: 기존의 반응성 시스템에서는 배열의 각 요소나 인덱스 변경을 모두 개별적으로 추적
  • ARRAY_ITERATE_KEY의 역할: 배열의 length 속성이나 정수형 인덱스가 변경될 때마다 자동으로 트리거
// depsMap은 Map으로, 배열의 각 속성과 인덱스에 대한 종속성을 가집니다.
depsMap = Map {
  'length' => Dep (length에 대한 종속성 집합),
  0 => Dep (인덱스 0에 대한 종속성 집합),
  1 => Dep (인덱스 1에 대한 종속성 집합),
  2 => Dep (인덱스 2에 대한 종속성 집합),
  // 배열의 크기만큼 인덱스에 대한 종속성이 생성됩니다.
}

// depsMap은 Map으로, 배열의 중요한 속성에 대한 종속성을 가집니다.
depsMap = Map {
  'length' => Dep (length에 대한 종속성 집합),
  ARRAY_ITERATE_KEY => Dep (배열 전체에 대한 종속성 집합),
  // 개별 인덱스에 대한 종속성은 최소화되거나 생략됩니다.
}

기존의 반응성 시스템에서는 배열의 요소와 인덱스에 대한 변경 사항을 개별적으로 추적
메모리 사용량 증가와 성능 저하의 문제 → ARRAY_ITERATE_KEY의 도입으로 배열 전체를 단일 종속성으로 관리

메모리 사용량을 줄이고 성능을 향상

computed 속성의 동작 방식이 개선

  • 변경 전의 computed 동작 방식
    • computed는 내부적으로 effect를 사용하여 종속성을 추적하고 값을 계산
    • 컴포넌트가 언마운트되면 해당 effect가 중지(stopped)되거나 비활성화
    • 이로 인해 computed 값이 오래된 상태(stale)로 남아있는 문제가 발생
  • 새로운 구조
    • computed는 이제 자체적인 구독자(subscriber) 타입으로 동작하며, 버전 카운팅(version counting)과 이중 연결 리스트(doubly-linked list)를 사용
    • effect에 의존하지 않으므로, 컴포넌트의 언마운트와 관계없이 항상 최신 값을 유지

버전 등 새로운 속성을 추가하면 무거워지는 단점이 있지 않을까?

  • 버전 번호와 같은 메타데이터는 반응성 시스템 내부에서 별도로 관리
  • 원본 데이터 객체에 직접 속성을 추가하지 않기 때문에, 데이터 구조가 불필요하게 무거워지지 않음
  • 버전 번호를 관리함으로써 얻는 성능 향상이 메모리 오버헤드보다 훨씬 크다고함

이중 연결 리스트 이전에는? → 종속성 관리를 위해 Set, Map 사용

  • 종속성 추가:
    • effect 실행 중에 반응형 데이터에 접근하면, 해당 데이터의 Depeffect가 ****구독자로 추가
    • effect는 자신의 종속성 목록에 해당 Dep을 추가
  • 종속성 제거:
    • effect가 다시 실행되기 전에, 이전에 등록된 모든 종속성을 순회하면서 각 Dep의 구독자 목록에서 effect를 제거
    • 이 과정은 순회가 필요하므로, 효율성이 떨어짐

이중 연결 리스트 자료구조 적용 후

  • 종속성 추가:
    • Depeffect가 서로를 양방향으로 연결하여, 종속성 추가 시 참조만 설정
  • 종속성 제거:
    • 종속성 제거 시 양쪽 노드의 참조를 변경하여 연결을 끊는 것만으로 제거가 가능
    • 이는 순회 없이 종속성을 제거할 수 있어 성능이 향상

그 외 유용한 변경점

Props 구조분해 할당

const props = withDefaults(
  defineProps<{
    count?: number
    msg?: string
  }>(),
  {
    count: 0,
    msg: 'hello'
  }
)

const { count = 0, msg = 'hello' } = defineProps<{
  count?: number
  message?: string
}>()

useId()

  • 서버와 클라이언트 렌더링에서 안정적이도록 보장되는 고유한 ID를 생성하는 데 사용할 수 있는 API
<script setup>
import { useId } from 'vue'

const id = useId()
</script>

<template>
  <form>
    <label :for="id">Name:</label>
    <input :id="id" type="text" />
  </form>
</template>

APP에 구현된 토스트 컴포넌트 id를 별로 라이브러리로 부여하고 있었는데 이걸 활용할 수 있을 것 같음

지연된 순간이동

  • <Teleport> 컴포넌트는 마운트될 때 대상 요소가 이미 DOM에 존재해야함
    • 이로 인해 Vue에서 렌더링한 다른 요소로 콘텐츠를 텔레포트할 수 없는 문제가 발생
    • 특히, 대상 요소가 <Teleport>보다 나중에 렌더링되는 경우에는 문제
  • defer 속성을 추가하면, <Teleport>는 대상 요소가 존재할 때까지 텔레포트를 지연
    • 이는 다음 렌더 사이클에서 대상 요소가 마운트되더라도 정상적으로 동작
<Teleport defer target="#container">...</Teleport>
<div id="container"></div>

0개의 댓글