blog.coinsect.io
🇰🇷
원문
마크다운
2024-06-13
899
공유

내가 React보다 Vue를 좋아하는 이유

### 1. Vue와 React의 간단 비교 React는 오랜 기간 왕좌를 차지해온 jQuery의 아성을 깨고 엄청난 인기를 얻는데 성공한 모던 프론트엔드의 대표 **UI 라이브러리**이다. 2024년 현재까지도 React의 지대한 인기는 식을 줄을 모른다. ![NPM 트렌드](https://d1085v6s0hknp1.cloudfront.net/chat/10368a5d-3977-48f1-acb3-5d4e1fb8f90f_image.png) [NPM 트렌드](https://npmtrends.com/angular-vs-jquery-vs-react-vs-svelte-vs-vue) 반면 Vue는 비교적 훨씬 뒤에 인기를 얻게 된 **프레임워크**이다. Vue는 센세이션을 몰고온 Angular의 `ng-model`, `ng-if` 등을 `v-model`, `v-if` 로 그대로 가져왔다. 프레임워크가 제공하는 기능들에 익숙해지면 대단한 생산성으로 빠르게 웹앱을 만들 수 있게 해준다. 물론 이런 프레임워크 종속적인 문법들을 알아야하는 Vue나 Svelte와 달리 모든 것이 자바스크립트인 React를 좋아하는 사람들도 많다. 그러나 상탯값을 `<input>`이나 `<textarea>` 등에 간단한 양방향 바인딩을 걸어 변형하는 기능조차 지원하지 않는 것은 상당한 귀찮음을 유발한다. React에서 다음과 같은 코드를 작성한다고 생각해보자. ```javascript const [val, setVal] = useState('') const [val2, setVal2] = useState('') const onInput = (e) => setVal(e.currentTarget.value) const onInput2 = (e) => setVal2(e.currentTarget.value) return <> <input onInput={onInput}> <input onInput={onInput2}> </> ``` Vue에서는 v-model을 이용해 다음처럼 구현할 수 있다. ```javascript <template> <input v-model="val"> <input v-model="val2"> </template> <script setup> const val = ref('') const val2 = ref('') </script> ``` 상태가 많아질수록 저런 인풋 이벤트 핸들러를 일일히 만드는 것은 아주 귀찮은 일이 된다. setState는 어떠한가? 다음의 예시를 보자. React ```javascript const [person, setPerson] = useState({ name: 'FRONT' }) return <div>{person.name}</div> ``` 여기서 렌더링된 `person.name`을 변경하려면, 우리는 변경하려는 코드에서 `setPerson({ ...person, name: 'BACK' })` 처럼 새로운 객체를 만들어 넣어주어야 한다. React는 객체 내의 특정 프로퍼티의 변경을 감지하지 못하고 객체의 레퍼런스가 바뀌어야 재렌더링을 하기 때문이다. 그러나 Vue에서는 `person.name = 'BACK'` 처럼 property를 수정하면 알아서 해당 DOM을 찾아 대상 값만 재렌더링한다. **Vue나 Svelte는 반응형 트랙킹을 환상적으로 지원해준다.** 그리고 이런 처리를 알아서 해주는 프레임워크들의 생산성이 압도적으로 높다는 점은 React를 사랑하는 개발자들도 부인할 수 없을 것이다. [![Rich Harris - Rethinking reactivity](https://img.youtube.com/vi/AdNJ3fydeao/0.jpg)](https://www.youtube.com/watch?v=AdNJ3fydeao) [Rich Harris - Rethinking reactivity](https://www.youtube.com/watch?v=AdNJ3fydeao) 물론 '마린의 HP가 1 감소하면 HP가 1 감소된 마린을 새로 만들어 리턴한다'는 함수형 프로그래밍의 관점에서는 모든 것이 immutable하다는 React의 접근이 더 어울리기는 한다. 이곳저곳에서 마구 데이터를 변형하며 그로인해 디버깅의 어려움이 초래되는 것은 괴로운 일일 것이다. 그러나 오랜 기간 Vue를 써오면서 반응형 처리 기능이 단점으로 느껴진 적은 단 한 번도 없었다. 개발자의 입장에서 프레임워크가 제공해주는 도구는 강력할수록 좋다고 생각한다. 그것을 사용하는 개발자의 숙련도가 중요한 것이다. React에서도 MobX를 사용하면 비슷한 경험을 할 수 있으나, 현재는 사실상 obsolete한 상태관리 툴이므로 권장하지 않는다. 그리고 Vue의 공식 문서에서도 지적하듯이 그럴 바에는 그냥 Vue를 쓰는 것이 낫다. (["React + MobX는 장황한 Vue이다."](https://v2.ko.vuejs.org/v2/guide/comparison.html)) --- ### 2. React는 프레임워크가 아니라 라이브러리이다. React는 jQuery와 마찬가지로 프레임워크가 아니라 라이브러리이다. 따라서 개발자들에게 특정 패턴의 코드라이팅을 강제하지 않는다. 이것은 높은 자유도를 의미한다. 라우트를 어떻게 구성할지, 상태관리 툴은 무엇을 사용할지, 컨텍스트를 사용할지, src 폴더 내의 구조는 어떻게 잡을지 등 모든 것이 자유롭다. 이런 높은 자유도는 숙련된 개발자가 원하는대로 앱을 구성해나갈 수 있는 여지를 주지만, "어떻게 앱을 만들어야 하는가?"에 관한 의견이 심지어 **숙련된 개발자**들 사이에서도 천차만별일 수 있다. 그리고 그 천차만별인 구조들은 다 유효한 관점일 수 있다. Vue에서는 vue-router가 디폴트이며, 상태관리 도구는 vuex에 이어 현재 pinia가 de facto이다. 그리고 플러그인들을 설정하면 별도의 import 없이도 `<template></template>` 내에서 플러그인들에 글로벌하게 접근할 수 있다. 이런 "마법"들은 코드베이스에 익숙하지 않은 사람들에게는 "대체 왜 작동하는 것인가?" 하는 멘붕을 유발할 수 있으나, 그들 역시 익숙해지고 나면 대단히 편리하게 사용하는 것을 보게 된다. 그리고 이는 co-worker들 사이에서 컨벤션에 대해 컨센서스를 잘 형성하고, `$`등의 prefix를 사용하는 등 유니크한 네이밍으로 어느 정도 커버가 가능하다. 예시: ```javascript // plugins.js import helpers from '@/helpers/index' import createBus from '@/helpers/bus' export default { install: (app) => { app.config.globalProperties.$helpers = helpers app.config.globalProperties.$translate = helpers.translate app.config.globalProperties.$toast = helpers.toast }, } // In every .vue file <template> <div>{{ $translate('VUE_PLUGIN_IS_MAGIC') }}</div> <button @click="$toast.success('I love Vue!')">Click</button> </template> ``` 단, 이 부분은 사실 템플릿에서 사용되는 모든 요소를 다소 귀찮더라도 .vue 파일 내에서 명시적으로 import하는 것에도 표를 줄 수 있다. IDE가 파일들 사이의 디펜던시를 추적하는데도, 그리고 상기한대로 모르는 사람이 코드를 봤을 때 혼란스러움을 느끼지 않도록 하는데도 마법에 의존하기보다는 명시적인 코딩이 도움을 주기 때문이다. (*"Explicit is better than implicit."* - Python) 어쨌든, Vue는 프레임워크라는 특성상 이런 opinionated된 요소들이 많기에 누가 Vue를 사용하건 대체로 비슷한 코드가 나올 수 밖에 없고, Vue가 제공하는 여러 마법들에 익숙해지면 상당히 많은 코드를 줄일 수 있다는 장점이 있다. --- ### 3. Vue에는 SSR용 공식 prefetcher가 있다. Vue에는 `onServerPrefetch` 훅이 존재한다. NODE 환경이 SSR인 맥락에서만 실행되는 훅인데, 여기서 비동기 데이터들의 prefetch를 비롯해서 SEO에 노출되었으면 하는 작업들을 할 수 있다. 즉 브라우저에서 우클릭 후 소스보기를 했을 때 HTML에 미리 SSR되기를 원하는 요소들을 여기서 미리 세팅해둘 수 있다. 이를테면 다음과 같다. ```javascript <template> <div class="my-component"> <!-- 심지어 라우트일 필요도 없다 --> {{ currentBlock }} </div> </template> <script setup> import { onServerPrefetch, ref } from 'vue' const currentBlock = ref(null) const loadBitcoinPrice = async () => { try { response = await fetch('https://blockchain.info/q/getblockcount') currentBlock.value = response.json() } catch (e) {} } onServerPrefetch(loadBitcoinPrice) </script> ``` 위와 같이 작성하면 SSR이 간단히 적용된다. 이것은 네이티브 Vue가 지원하는 훅이다! 따라서 Vue로 SSR 앱을 만드는 경우 굳이 Nuxt와 같은 메타 프레임워크를 쓸 필요가 없다. 물론 `renderToString` 등의 함수를 실행해줄 SSR 서버를 직접 구현해야하는 어려움은 있다. 단 한 번도 해본적이 없다면 상당한 진입장벽일 수 있다. 또한 클라이언트가 실행되었을 때 currentBlock의 채워진 내용을 유지하려면, 서버에서 populate된 데이터를 클라이언트로 전달하는 다음과 같은 (흔한) 방법을 사용해야 한다. 1. SSR시 클라이언트로 전달할 상태를 직렬화한 후, `window.__INITIAL_STATE__` 등의 변수로 할당해 HTML에 같이 넣기 ```markup <script>window.__INITIAL_STATE__ = ${JSON.stringify(store.state)}</script> ``` 2. 클라이언트 로딩시 위의 문자열을 읽어 다시 클라이언트 상태들에 populate시키기 ```javascript store.replaceState(window.__INITIAL_STATE__) ``` 그래서 보통은 컴포넌트 내의 로컬 상태들을 SSR에 사용하기보다는, 전체 상태를 간단하게 갈아치울 수 있는 `replaceState` 등을 지닌 Vuex와 같은 상태관리 툴을 사용하는 것이 더 용이하다. 즉 `onServerPrefetch` 훅에서 `store.dispatch(서버상태)`를 한 후, 그것을 템플릿에서 인용하는 것이 보다 구조적으로 깔끔하다. 앱이 로딩될 때 전역적으로 스토어의 전체 내용을 한 번 갈아끼우는 구조로 구성하는 것이 편리하기 때문이다. React에는 이런 훅이 없으므로 Remix (`loader`)나 Nextjs (`getServerSideProps`)가 제공하는 방식을 따르거나, 아니면 직접 SSR을 구현하는 경우 컴포넌트마다 prefetch가 필요할 때 실행할 훅을 정의하고, 어떻게 서버에서 그 훅을 실행하여 데이터를 populate시켜두고 클라이언트에서 hydrate시킬지 잘 생각하여 직접 구현해야 한다. 그러나 이런 룰을 직접 정하는 것이 별로 좋지 않고 구현도 복잡하므로, 그냥 Remix나 Nextjs를 사용하는 것을 추천한다. 직접 SSR을 해보는 것은 대단히 많은 공부가 되니, 혹시 이 과정을 잘 모른다면 한번쯤 도전해보기를 권한다. 황준일님의 다음 글이 참고하기에 매우 좋다. ([Vue SSR 제대로 적용하기 (feat. Vanilla SSR)](https://zuminternet.github.io/vue-ssr/)) 비단 Vue 뿐만 아니라 태생적으로 SSR이 매우 불편한(?) SPA가 SSR되는 과정이 잘 설명되어 있다. --- ### 4. Vue에는 `useMemo`, `useCallback` 등이 없다. React는 `useMemo`, `useCallback`을 통해 계산된 값이나 메소드를 재실행하지 않고 레퍼런스를 유지할 수 있는 방법을 제공한다. 이 덕에 이런 값들을 쓰는 자녀 컴포넌트가 불필요한 재렌더링을 하지 않도록 최적화할 수 있다. 그러나 나는 개발자가 이런 렌더링 최적화를 생각해가며 코딩해야하는 상황을 보면서 마치 C에서 `malloc`과 `free`를 직접 하는 듯한 기분을 느낀다. 지금은 2024년이다. 이정도는 우리가 쓰는 도구들이 자동으로 해줘도 괜찮다. 더군다나 최적화와 관련된 코드들은 올바른 방식으로 사용되지 않으면 오히려 evil이다. Vue의 런타임은 자동으로 dependency를 추적하여 렌더링 최적화를 하기 때문에, 개발자는 비즈니스/뷰 로직에만 집중할 수 있다. [Comparison with React Hooks](https://vuejs.org/guide/extras/composition-api-faq.html#comparison-with-react-hooks)에서는 다음과 같은 점을 언급한다. - Vue's runtime reactivity system automatically collects reactive dependencies used in computed properties and watchers, so there's no need to manually declare dependencies. - No need to manually cache callback functions to avoid unnecessary child updates. In general, Vue's fine-grained reactivity system ensures child components only update when they need to. Manual child-update optimizations are rarely a concern for Vue developers. 개인적으로 코드 리뷰시 올바로 useMemo, useCallback 등이 쓰였는지, 아니면 그것들을 사용하지 않는 구조로 컴포넌트들을 더 잘 구성할 수는 없는지 등을 살펴보며 시간을 낭비할 필요가 없게 자동으로 렌더링을 최적화해주는 Vue의 접근이 훨씬 좋다고 생각한다. React도 19부터는 React Compiler가 추가되며 useMemo, useCallback 등이 deprecated될 예정이다. 또한 자녀 컴포넌트의 ref를 참조하기 위해서는 `forwardRef`로 감싼 HOC를 만드는 것이 강제되었었는데 이 역시 prop으로 간단히 쓸 수 있게 변경된다. 이런 편리한 점들은 어찌보면 당연히 있어야 하는 설탕들인데 너무 늦게 생긴 느낌이다. 개인적으로는 커스텀 컴포넌트에 대한 `className`도 그냥 default prop 중 하나가 되었으면 하는 바람이 있다. ![React 19 is coming](https://blog.lama.dev/images/react-19-new-features.png) [React 19 is coming](https://blog.lama.dev/react-19-is-coming/) --- ### 5. Vue의 CSS module과 SFC는 편리하다. Vue는 SFC를 지원한다. 따라서 한 파일 내에서 위 아래로 스크롤 하면서 스타일시트를 확인할 수 있는데, 개인적으로는 별도로 css를 작성할 수 밖에 없는 React에 비해 SFC가 편리하게 느껴진다. (~~SFC가 불편할 정도로 파일을 길게 작성하는 습관이 있다면 그 자체가 문제이다.~~) 클래스명을 파일 내에서 검색하면 `<template>`과 `<style>`을 빠르게 왔다갔다 할 수 있다는 장점은 파일이 분리되어 있는 경우 누릴 수 없다. 물론 CSS-in-JS가 있긴 하지만 이것은 JS가 다 로드되기 전에는 CSS를 알 수 없어 FOUC가 발생하므로 SSR시 미리 스타일시트 파일을 따로 만들어 넣어주는 등의 꼼수가 필요하다. 또한 React에서 scoping을 위해 사용하는 기본 방법인 CSS module처럼 scoping이 필요한 경우 단순히 `<style scoped>` 처럼 scoped 예약어만 추가하면 된다는 점도 편리하다. 이것이 실제 핸들링되는 방식에는 다음과 같은 차이가 있다. 1. React에서는 클래스명 자체가 변형된다. (e.g. `.app-header` => `_app-header_4nd0i_1`) 2. Vue에서는 클래스명과 `data`로 시작하는 특수한 attribute의 조합으로 scoping을 한다. (e.g. `.app-header[data-v-4d32bb86]`) 글로벌하게 `.app-header`의 스타일을 변형하고 싶은 니즈가 있는 경우 React의 경우 원래 클래스명에 더해 글로벌한 클래스명을 한 번 더 적어줘야하는 반면, Vue의 경우는 원래의 클래스명을 변경하지 않으므로 자동 적용된다. --- ### 마침 그럼에도 불구하고 구직시장을 보면 여전히 React가 압도적이다. React 이외의 다른 도구를 원하는 회사는 별로 없다. 따라서, 누군가 내게 **"단 하나의 도구만 익혀야 한다면 무엇을 해야하는가?"** 라고 묻는다면 1초도 망설임없이 React라고 답한다. 무엇이든 기본이 가장 중요하다. 프론트엔드를 잘하기 위해서는 HTML, CSS, JS를 잘하면 되고, 나아가서는 React 하나면 충분하다. React가 가장 어렵기 때문에, React를 능숙하게 익힌다면 Vue, Svelte 등등 다른 도구들도 얼마든지 수월하게 배울 수 있다. React의 대세가 금방 꺾일 것 같지는 않지만, React의 주류 메타 프레임워크인 Nextjs가 잦은 변경사항과 불친절한 문서로 최근에 악명을 얻고 있다. (Vue는 공식 문서가 아주 좋은 것으로 유명하다.) [Web Dev Simplified](https://www.youtube.com/watch?v=3RyguimNe8s)나, [Fireship](https://www.youtube.com/@Fireship) 등의 대형 유튜버들도 이런 점들에 대한 불편함을 공개적으로 언급하고 있다. 프론트엔드의 유행이 빠르게 바뀐다는 점을 생각해보면 React가 이토록 오래 터줏대감 노릇을 하는 것이 다소 이해되지 않기는 한다. 어쨌든 아직까지도 React가 대세이니, 취업이나 이직을 준비중이라면 망설임 없이 React를 선택하면 된다.
1