인사이트

인사이트

새로운 관점으로 깊이 있는 통찰을 제시합니다.

SW 테크놀로지

Vue.js 3.0 무엇이 달라졌는가?

2021.04.21백은제
다운로드

2014년 첫 출시된 Vue.js가 2020년 9월, 버전 3.0으로 업그레이드 되었다. Vue.js 3.0의 개발이 시작된 지 약 2년 만이었다. Vue.js는 더 쉽고, 가볍고, 누구나 빨리 배울 수 있는 접근성이 뛰어난 프레임워크라는 방향성을 가지고 개발되었다. Vue.js는 기존 HTML 마크업 기반의 템플릿을 그대로 활용하며 CSS를 작성하는 스타일도 기존 문법을 그대로 따른다. 이 때문에 프레임워크를 처음 접하는 사용자들이 진입하기에 부담스럽지 않다는 장점이 있다.

또한 확장성도 빼놓을 수 없다. 제이쿼리(jQuery)처럼 스크립트 태그로 CDN(Content Delivery Network)을 추가할 수 있고 프로그램에 따라 점진적으로 라이브러리를 채택할 수 있다. 아울러 Vue.js는 라우팅∙상태 관리∙빌드 도구 등 공식적으로 지원하는 라이브러리와 패키지를 통해 배포한다. 이러한 확장성은 웹 개발을 더욱 단순하고 쉽게 만들어 준다.

 

그림1 Vue.js의 공식 로고입니다. 알파벳 브이 모양을 띄고 있습니다.

 

본 아티클에서는 Vue.js 3.0이 기존 버전의 한계점을 어떤 방식으로 해결했으며 개발자 측면에서 어떤 점이 달라졌는지 가장 중요한 변화들을 기준으로 소개해보고자 한다.

 

 

성능 향상

 

가상 DOM(Document Object Model) 최적화

기존 Vue.js의 렌더링을 위한 가상 DOM 설계는 HTML 기반의 템플릿을 제공하고 이 템플릿 구문을 가상 DOM 트리로 반환한 후 실제로 DOM의 어떤 영역이 업데이트되어야 하는지 재귀적으로 탐색하는 방식이었다. 이 작업은 불필요한 탐색이 많이 포함될 수 밖에 없었다. 그 이유는 무엇일까? 변경 사항을 파악하기 위해 전체 DOM 트리를 재귀적으로 탐색해야만 하는 상황이 있다고 가정해보자. 변경이 필요한 부분만 확인하는 것이 아니라 매번 전체 트리를 모두 확인해야 하는 동작은 비효율적이다. 만약 템플릿 구문에서 정적인(static) 구문이 대부분을 차지하고 동적 바인딩은 적은 경우라면 그 비효율성은 더욱 커지게 된다. Vue.js는 이같이 불필요한 탐색을 위한 코드를 제거하여 렌더링 성능을 향상시키고자 다음과 같이 가상 DOM의 최적화 작업을 진행했다.

첫 번째로 템플릿 구문에서 정적 요소와 동적 요소를 구분하여 트리를 순환할 때 동적 요소만 순환할 수 있도록 했다. 미리 구문 내의 정적인 영역을 블록으로 구분한다. 렌더링을 할 때 동적 요소가 있는 코드가 영향을 받게 되면 정적인 영역으로 구분해 둔 블록에는 접근하지 않고 동적 요소가 있는 코드에만 접근하여 렌더링 트리의 탐색을 최적화하는 방식이다.

두 번째로 렌더링 시 객체가 여러 번 생성되는 것을 방지하기 위해 컴파일러가 미리 템플릿 구문 내에서 정적 요소∙서브 트리∙데이터 객체 등을 탐지해 렌더러(Renderer) 함수 밖으로 호이스팅(Hoisting) 하는 것이다. 이를 통해 렌더링 시 렌더러마다 객체를 다시 생성하는 것을 방지하여 메모리 사용량을 낮췄다.

세 번째로 컴파일러가 미리 템플릿 구문 내에서 동적 바인딩 요소에 대해 플래그를 생성한다. 예를 들면 특정 요소가 동적 클래스 바인딩을 가지고 있고 정적인 값이 지정된 속성을 갖고 있다면 클래스만 처리하면 된다. 따라서 컴파일러가 미리 생성해 둔 플래그로 필요한 부분만 처리하여 렌더링 속도를 향상시킬 수 있었다.

 

트리 쉐이킹(Tree-shaking) 강화

트리 쉐이킹이란 나무를 흔들어 잎을 떨어트리 듯 모듈을 번들링 하는 과정에서 사용하지 않는 코드를 제거하여 파일 크기를 줄이는 최적화 방안을 의미한다. Vue.js 3.0은 템플릿 컴파일러가 실제 사용하는 코드만 임포트(Import) 하도록 했다. 양방향 데이터 바인딩을 지원하는 v-모델 디렉티브와 같은 대부분의 사용자 정의 기능에서 트리 쉐이킹이 가능했기 때문에 Vue.js 3.0에서 이를 강화하여 번들 크기를 절반 이상으로 대폭 줄일 수 있었다.

 

표1. 크롬 브라우저에서 Vue.js 버전2와 Vue.js 버전3의 성능을 비교한 것입니다. 프리 컴파일드 항목을 중점적으로 보면 됩니다. Vue.js 3.0 개발팀은 릴리즈 노트에서 가상 DOM 최적화, 트리 쉐이킹 강화 등을 통해 이전 버전에 비해 초기 렌더링은 55%, 업데이트 속도는 133% 빨라졌으며 메모리 사용량은 54% 감소했다고 밝혔습니다.

 

Vue.js 3.0 개발팀은 릴리즈 노트에서 가상 DOM 최적화, 트리 쉐이킹 강화 등을 통해 이전 버전에 비해 번들 크기가 최대 41% 감소하였고 초기 렌더링은 최대 55% 빠르며 업데이트와 메모리 사용량은 최대 133% 빨라졌다고 밝혔다. [표 1]은 실제로 2.0 버전과 3.0 버전을 비교하여 크롬(Chrome) 환경에서 성능 테스트를 해본 사용자들의 리포트이다. Vue.js 개발팀이 밝힌 바와 같이 이전 버전에 비해 눈에 띄는 성능 향상을 보이고 있다. 성능이 필수적인 프론트엔드 프레임워크에서 더 빠르고 더 작은 프레임워크로 나아가고자 하는 방향성이 돋보이는 업데이트라고 생각된다.

 

 

컴포지션(Composition) API의 등장

 

컴포지션 API는 함수 기반의 API로 Vue.js 3.0의 핵심이라고 할 수 있다. 컴포지션 API가 왜 등장하게 되었는지를 설명하기 위해서는 기존 Vue.js의 한계점에 대해 알아야 한다. 다음 세 가지의 포인트로 설명하겠다.

– 프로젝트의 규모가 커질수록 코드의 복잡성이 증가

– 코드를 재사용 할 때 존재하는 한계점

– 제한된 타입스크립트 지원

 

1) 프로젝트 규모에 따라 커지는 코드의 복잡성

Vue.js의 사용자층이 늘어감에 따라 대규모 프로젝트에 대한 지원이 필요해졌다. 여러 개발자들이 하나의 프로젝트에 오랜 기간 매달리고 유지보수 하는 경우 이전 버전의 Vue.js는 한계점이 분명히 드러났다. 하나의 컴포넌트를 개발하는 데 여러 기능이 함께 들어가야 하는 경우를 생각해보자. 하나의 기능에 대해 Vue 인스턴스의 데이터 영역에 변수 선언이 들어갈 것이고, 메소드(Methods) 영역에는 함수가, 컴퓨티드(Computed) 영역에는 계산 속성이 들어갈 것이다. 이 외에도 라이프사이클 훅(Hook)이나 와치(Watch) 등 더 많은 부분으로 로직이 분리된다. 이렇게 여러 개의 기능이 나누어져 코드 안에 뒤섞이게 되면 컴포넌트의 크기가 커질수록 복잡성이 증가한다. 복잡한 코드는 가독성이 좋지 않고 논리적으로도 읽기가 힘들어진다.

 

그림 2 옵션 API와 컴포지션 API를 비교하고 있습니다. 컴포넌트를 색깔별로 표시하고 있습니다. 옵션 API는 14개의 컴포넌트로 잘게 나뉘어 구성된 반면 컴포지션 API는 5개의 커다란 컴포넌트로 구성되어 있습니다. 옵션 API의 구조가 파편화 되어 있음을 알 수 있습니다.

 

컴포지션 API는 모든 코드를 독립적으로 정의할 수 있다. 각 기능을 함수로 묶어 처리하기 때문에 특정 기능의 유지 보수를 위해 해당 함수만 확인하면 된다. 이전 버전에서 제공하던 옵션(Options) API는 자바스크립트 프레임워크를 처음 접하는 사람이라면 더 직관적으로 와닿을 수도 있다. 그러나 컴포넌트의 크기가 커질 경우 하나의 기능에 대한 코드가 여러 부분에서 작성되어 파편화되면서 유지 보수가 어려워진다. 따라서 컴포지션 API를 통해 기능을 모듈화하여 구성하는 것이 더 나은 선택이 될 수 있다.

 

2) 코드 재사용이 어려움

이전 버전의 Vue.js에서도 믹스인(Mixin)이나 슬롯(Slots) 등으로 컴포넌트 코드를 재사용할 수 있었다. 컴포넌트 로직을 재사용하기 위해 주로 이용되었던 믹스인은 한계점이 존재했다. 프로젝트가 커지고 믹스인을 사용해 다중으로 상속하게 되면 컴포넌트 관리가 어려웠다. 예를 들어 각 기능의 속성이 병합될 때 이름 충돌이 발생하기 쉬워 개발자 측면에서 네이밍에 대한 명확한 컨벤션이 필요했다. 또한 매개 변수를 믹스인을 통해 전달할 수 없어 코드 재사용 시 유연성이 떨어졌다.

여기서 컴포지션 API를 사용하면 인스턴스의 특정 기능 단위로 모듈화된 로직을 여러 컴포넌트에서 재사용할 수 있게 된다. 이는 대규모 프로젝트에서 로직의 유연성을 높여준다.

 

3) 제한된 타입스크립트 지원

Vue.js 3.0은 타입스크립트를 더욱 적극적으로 지원하기 위해 코드베이스를 타입스크립트로 작성했다. 이를 통해 개발자는 Vue CLI로 타입스크립트 또는 자바스크립트를 사용하여 추가 도구 없이 Vue 앱을 생성할 수 있다. 물론 이전 버전의 Vue도 타입스크립트를 지원했다. 데코레이터를 이용하여 클래스 기반의 API를 선언하거나 Vue.extend로 컴포넌트를 정의해 기존 객체 구조 방식대로 사용할 수 있었다. 다만 Vue.js가 기존에 옵션 API를 중심으로 객체 구조 방식을 사용하고 있었기 때문에 타입스크립트를 온전히 사용하기에는 한계가 있었다. 타입스크립트의 타입 추론 방식은 타입을 명시적으로 선언하지 않아도 추론이 가능해야 하는데 객체 구조 특성상 개발자가 일일이 타입을 정의해 줘야 하는 상황이 많았기 때문이다. Vue.js 3.0은 컴포지션 API와 함께 타입스크립트를 사용하기 위해서 컴포지션 API 내부의 setup() 함수에서 자동으로 타입을 추론하기 때문에 사용하기가 훨씬 수월해졌다.

 

 

그 외 주목할 만한 변화

 

텔레포트(Teleport)

텔레포트는 리액트(React)에서 기본으로 제공하는 포탈(Portals)과 유사한 기능이다. Vue.js가 기존에 Portal-vue라는 플러그인을 통해 제공하고 있던 기능이기도 하다. 다음과 같은 상황에서 텔레포트가 유용하게 사용된다.

모달이나 알림 등과 같은 요소를 렌더링하려는 위치가 템플릿 구문이 속하는 컴포넌트와 다른 컴포넌트에 있을 때는 보통 모달이 포함된 컴포넌트를 하나 더 만들어 컴포넌트의 구조를 변경해야 한다. 다른 태그 위치로 모달의 위치를 조정하는 것을 CSS를 통해 해결하기가 까다로웠기 때문이다. Vue.js 3.0은 텔레포트를 사용하여 모달 컴포넌트를 분리하지 않고도 <teleport> 태그 내부의 HTML을 특정 태그로 옮겨 렌더링 할 수 있게 되었다.

 

프래그먼트(Fragment)

이전 버전의 Vue는 템플릿 구문에 단 하나의 <div> 태그만 허용했다. 그 이유는 Vue 인스턴스를 단일 DOM 요소로 바인딩 해야 했기 때문이다. 프래그먼트를 사용하면 다중 루트 노드를 가질 수 있다. 프래그먼트는 실제 DOM 트리에서는 렌더링 되지 않는 컴포넌트이기 때문에 중복 DOM 노드가 생기지 않으면서도 다중 루트 노드를 가질 수 있게 된다.

 

서스펜스(Suspense)

서스펜스 컴포넌트는 리액트가 지원하던 컴포넌트 종류 중 하나이다. 서스펜스는 컴포넌트 내에 있는 조건인 Async 구문이 충족되지 않으면 조건이 충족될 때까지 템플릿 내에 Fallback 구문을 렌더링한다.

컴포지션 API를 통해 Setup() 함수 내에서 외부 API에 접근해 데이터를 가져오는 비동기 작업을 수행하면 데이터를 모두 가져올 때까지 로딩 표시를 해야 할 수 있다. 이럴 때 서스펜스를 사용해 컴포넌트를 감싸면 대체할 템플릿 구문을 렌더링 할 수 있다. 그렇다면 데이터를 가져오는 도중 오류가 발생하면 어떻게 될까? Vue.js 3.0은 새로운 라이프사이클 훅(hook)인 OnErrorCaptured를 제공한다. 오류가 발생한 경우 OnErrorCaptured를 사용하여 에러를 잡아낼 수 있고 해당 에러에 대한 처리 구문을 Fallback 구문 대신 표시할 수 있다.

 

리액티비티(Reactivity) API

이전 버전의 Vue.js는 인스턴스 내부에 오브젝트를 선언하고 새로운 속성을 추가하는 것을 감지할 수 없었다. 그래서 기존에는 Vue.set 메소드를 사용하여 기존 객체에 반응성을 부여했다. Vue.js 3.0은 이러한 데이터 반응성을 해결하기 위해 리액티비티 API를 지원한다.

객체에 반응성을 추가하기 위해서는 리액티브(Reactive) 메서드를 사용하면 된다. 단순 값이라면 Ref 메서드를 사용한다. 이 외에도 Readonly, ToRef 등 반응성을 지원하는 여러 API가 추가되었다. 세부 사항은 Vue.js 3.0 공식 사이트(https://v3.vuejs.org/guide)에서 확인할 수 있다.

 

 

마치며

 

지금까지 보다 다양한 개발자 경험을 제공하려는 Vue.js 3.0의 개선 사항을 엿볼 수 있었다. 기존 Vue.js의 한계점을 극복하기 위한 노력도 돋보였지만 타 사의 라이브러리나 프레임워크에서 제공하는 기능을 탑재한 것이 의미심장 했다. 그 중 가장 큰 변화로 다가오는 것은 역시 컴포지션 API의 등장이다. 이 글을 읽는 누군가는 그럼 이제 옵션 API로 개발할 수 없게 된 것인지 당황해 할 수 있을 것이다. 그러나 Vue.js 3.0에서도 옵션 API를 여전히 사용할 수 있으니 큰 걱정은 하지 않아도 된다.

현재 진행 중인 프로젝트의 문제와 특징을 파악하고 이를 해결하기 위해 어떤 솔루션이 적합할지 고민해 보는 시간을 가져 보는 것은 어떨까? Vue.js 3.0과 함께 보다 효율적이고 유지 보수가 쉬운 프로젝트를 완성할 수 있을 것이다.

# References

- https://github.com/vuejs/vue-next/releases/tag/v3.0.0
- https://increment.com/frontend/making-vue-3/
- https://v3.vuejs.org/guide/
- https://vuejs.org/v2/guide/
- https://vueschool.io/articles/vuejs-tutorials/exciting-new-features-in-vue-3/
- https://geckodynamics.com/blog/vue2-vs-vue3

백은제 프로

백은제 프로

에스코어㈜ 소프트웨어사업부 개발플랫폼그룹

에스코어에서 UI Dev 플랫폼 개발 및 유지보수를 담당하고 있습니다.