Vue3에서는 Vue2에서 플러그인 형태로 지원되던 Composition API가 공식 API로 채택되었다.
(React hooks의 영향을 받아서인지) Composition API는 React hooks와 상당히 유사하다.
이를 통해 컴포넌트 로직을 유연하게 구성할 수 있도록 하여 재사용성을 높이고 가독성을 향상시킨다는 점에서 Vue2 Option API와 가장 큰 차이점이라고 볼 수 있다. 개인적으로는 더 자바스크립트스러워졌다(?)는 느낌도 든다.
이 글에서는 React와 Vue3를 비교해보면서 차이점을 정리해본다.
SFC와 reactivity(반응성)
우선 React와 Vue에서 각각 상태를 어떻게 선언하고 렌더링하는지 코드로 보자.
React
Vue
Single File Component (SFC, 싱글 파일 컴포넌트)
하나의 컴포넌트와 관련된 코드(HTML, CSS, JS)를 하나의
.vue
파일에서 관리하는 방법을 싱글 파일 컴포넌트라고한다. 다른 프레임워크와 다른 Vue.js의 특징 중 하나이고 이러한 .vue
파일은 웹팩 로더의 한 종류인 vue-loader에 의해서 HTML, CSS, JS로 분리된다.Reactivity (반응성)
React 문서를 보면 Reconciliation(재조정)라는 단어가 유독 많이 등장하는데 Vue.js 문서에서는 Reactivity(반응성)라는 용어를 많이 강조한다.
Vue.js의 핵심 컨셉은 reactivity를 통해서 상태 변화에 반응하는 것인데 Vue3에서는 자바스크립트의 Proxy 객체를 활용하여 reactivity를 구현 하고있다. 쉽게 말해 ref나 reactive와 같은 Vue의 반응성 API를 사용하면 변수에 reactivity가 주입되어 해당 변수에 대해서 변경내용을 추적하고 변화를 바탕으로 Proxy 객체가 반응해서 UI를 변경한다.
렌더링 과정
React와 Vue 모두 Virtual DOM을 사용하지만 디테일하게 보면 렌더링 과정에서 차이점을 찾아볼 수 있다.
React
React의 함수형 컴포넌트는 그 자체로 render() 함수다. 그래서 state, props와 같은 반응형값이 바뀌었을 때 함수 자체가 다시 실행되면서 리렌더링이 발생하고 변경된 부분만을 업데이트하는 Reconciliation이 일어나서 실제 DOM에 반영한다.
Vue
Vue에서는 ref, reactive와 같이 Reactivty가 주입된 데이터를 추적하고 해당 변경 사항을 감지하여 변경된 부분만을 가상 DOM에 반영한다.
실제로 vue3에서는 자바스크립트의 Proxy객체로 데이터를 래핑해서 변경을 추적한다.
즉, react는 렌더링간에 컴포넌트가 다시 실행되면서 이전 렌더링과 현재렌더링간의 차이를 계산해서 DOM에 업데이트하고 vue는 데이터의 변경을 추적하고 변경된 데이터의 부분만 실제 DOM에 업데이트하는 것
코드로 비교해보기
React를 하던 사람이라면 어떤 변수가 기존의 state를 의존하고 있을 때 이 값은 새로운 state로 만들지 않고 컴포넌트 안에서 로컬 변수로 사용할 것이다. 왜냐하면 state가 변경되면 다시 컴포넌트 렌더링이 일어나면서 함수가 실행되기 때문에 fullName 변수가 다시 정의되기 때문이다.
하지만 이건 어떨까? 처음 마운트될 때는 화면에는 잘 나오겠지만 fullName이라는 변수는 Reactivty 가 주입되지 않아 이후에 name이 변경됨에 따라 반응하지 않는다.
따라서 computed 함수를 통해서 데이터에 Reactivty를 주입해줘야한다.
Reactivity 시스템
Vue ref함수는 React의 useState() 와 매우 유사하며, React useRef()훅에서 데이터에 ref.current로 접근하는 것처럼 Vue의 ref는 ref.value로 접근한다.
아래 예시에서 myCounter 데이터에 접근하려면 myCounter.value로 접근 해야하는데 이건 script 태그 안에서만 적용되고 template에서는 그냥 myCounter만 적어도 된다.
React는 이전 상태값과 이후 상태값을 비교해서 다른 경우에만 업데이트를 한다. 따라서 React의 state는 불변은 지켜야하고 state가 배열이나 객체의 참조 타입의 경우 상태 변경은 기존값의 수정이 아닌 새로운 객체를 생성 해야한다.
반면 Vue는 객체의 속성을 직접 수정하는 것이 가능한데 이는 객체의 속성 변경을 감지하고 리렌더링을 트리거하는 reactivity 시스템을 갖고 있기 때문이다.
Custom composition (a.k.a Custom Hooks)
React에서 공통 로직을 분리해서 Custom hook으로 관리할 수 있다면 Vue에서는 Custom composition이 있다. (사실 사내에서는 그냥 훅이라고 부른다.) 보다시피 Custom hook을 사용해 봤다면 어렵지 않게 사용할 수 있고 이름 또한 use라는 접두사를 보편적으로 사용하고 있어서 더 유사하다고 느껴진다.
이렇게 Custom composition을 이용해 컴포넌트에서 상태 관리, side effect 처리 등의 로직을 분리하고 재사용할 수 있다.
Side Effect
Vue3의 Composition API에서 watch는 reactivty가 주입된 변수를 추적하고 변경되었을 때 추가적인 로직을 실행하는 주는 함수다. React의 useEffect hook과 매개변수 순서만 다르고 사용법은 똑같다.
여기서 Vue에서는 재밌는 훅이 있는데, watch말고 watchEffect라는게 있다. watchEffect는 기본적으로는 watch와 매우 유사하다. 의존성 배열을 넘기지 않으며 watchEffect 내부에 reactivity가 주입된 값들을 모두 추적하고 그 값들중 하나라도 변하면 effect 함수가 실행된다.
Vue의 세심함(?)이 느껴지는 부분이라는 생각도 든다. 어처피 의존성들을 다 추적할거라면 굳이 의존성 배열에 다 명시하지 않아도 되고 watchEffect를 사용하기면 하면 된다는 배려가 될 수도 있다.
다른 한편으로는 watchEffect 내부가 복잡해진다면 무슨 값들을 추적하고 있는지 명시하고 있지 않으므로 개발자가 파악하기 어려울 수 있다. 이는 개발자의 생산성과 유지보수성을 해칠 수도 있다.
watch와 watchEffect에 대해서는 아래 글에서 자세히 다룬다.