본문 바로가기

FrontEnd

Vue3과 React 훅의 반응성 비교 : 불변 VS 가변

반응형

리액트와 Vue3의 반응형 구현은 어떤 차이가 있을까요?

1. 리액트는 불변 모델을 사용합니다. 리액트는 데이터와 UI를 동기화합니다.

https://itchallenger.tistory.com/143 게시물에서 설명했듯이,

리액트는 상위 컴포넌트 렌더링이 반드시 하위 컴포넌트 트리 전체를 리렌더링 합니다. (중간에 memo같은 장벽이 없으면요)

여기서 렌더링이란 리액트 컴포넌트를 만드는 함수를 호출하는 것을 의미하며

실제 돔에 mount 하는 것을 의미하지는 않습니다.

 

useEffect는 사이드이펙트와 UI의 동기화를 의미합니다.

useEffect는 컴포넌트가 마운트 된 후, 그리고 언마운트 클린업 시, 혹은 업데이트 후에 콜백을 호출합니다.

또한 해당 훅 내부에서 UI과 관련한 사이드이펙트를 실행하는 것은 안전합니다.

(개발자가 잘못 코딩한 것이 아니라면요...)

 

2. Vue3은 가변 모델을 사용합니다. Vue3은 컴포넌트 라이프사이클과 UI를 동기화합니다.

vue3은 프록시를 사용한 의존성 추적을 이용해, 어떤 데이터가 변경되었을 때 엘리먼트가 리렌더링되어야 하는지를 결정합니다.

리액트의 상위 컴포넌트의 상태가 변경되면 하위 컴포넌트가 전부 리렌더링되는 모델과 약간 다릅니다.

즉 vue3의 리렌더링은 의존성 관계에 의해서만 일어나며, 정적 바인딩과 동적 바인딩을 구분합니다.

  • 이는 상위 컴포넌트의 리렌더링이 모든 자식 엘리먼트의 리렌더링을 유발하는 것이 아님을 의미합니다.
  • 반대로 자식 컴포넌트의 리렌더링이 상위 컴포넌트의 리렌더링을 유발하는 멘탈 모델(ex : emit)을 생각할 수 있습니다.
    • (리액트의 콜백을 이용한 상단에서 하단으로의 단방향 리렌더링 모델과는 약간 다릅니다.)
  • 동적 바인딩(v-bind)과 정적 바인딩(props)이 구분됩니다. 정적 바인딩의 경우, 한번 렌더링 되면 변화할 일이 없습니다.

즉, vue는 템플릿 각각의 요소가 개별적으로 렌더링 사이클을 갖게 됩니다.

vue3의 컴포넌트 라이프 사이클

우리는 리액트를 사용할 때 리액트 자체의 생명 주기에 대해 크게 신경쓰지 않아도 되었습니다.

그냥 돔이 있다 없을때, 업데이트 될 때, 즉 선언적인 UI의 상태를 기반으로 화면을 구현할 수 있었죠.

 

하지만 vue3는 약간 다릅니다.

vue3은 컴포넌트의 라이프사이클과 UI를 동기화하려 시도합니다.

beforeMount 이전에 컴파일이 완료되면, 해당 컴포넌트에는 언마운트 되기 전까지 가변 상태가 존재합니다.

게다가 watch, watchEffect 이 친구들은 컴포넌트가 업데이트(마운트) 되기 이전에 호출 된다고 합니다.

이 말은 마운트 이전 혹은 돔 변경 후에 watchEffect에서 돔을 접근하는 사이드 이펙트를 수행하면 돔 접근을 못할 수 있음을 의미합니다.

즉 아래의 trigger re-render 단계에서 이펙트 함수가 호출되는 것이며,

이는 위 그림의 beforeUpdate 단계에 해당합니다.

업데이트 이전은 리렌더링 및 패치 이전을 의미합니다.

가상 DOM 트리의 복사본이 두 개 있는 경우 렌더러는 두 트리를 살펴보고 비교하여 차이점을 파악하고
이러한 변경 사항을 실제 DOM에 적용할 수도 있습니다.
이 과정을 Reconciliation, diffing, Patch 라 합니다.

즉 우리는 마운트 / 업데이트 / 언마운트 시에 watchEffect에 플래그를 줄 필요가 있는지 없는지 결정해야 합니다.

이는 성능상 이점(prefetch)를 가져올 수도 있지만, 실수를 유발할 수도 있는 지점이라고 생각합니다.

watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'post'
})
... 기본적으로 사용자 정의 Watcher 콜백은 Vue 컴포넌트가 업데이트되기 전에 호출됩니다.
즉, watcher 콜백 내에서 DOM에 액세스하려고 하면 DOM이 Vue에서 업데이트를 적용하기 전의 상태가 됩니다.

Vue가 Dom을 업데이트한 후 액세스하려면 flush: 'post' 옵션을 지정해야 합니다.
공식문서 발췌 > https://vuejs.org/guide/essentials/watchers.html#callback-flush-timing

그리고 리액트와 다르게 클린업은 unmounted 라이프사이클 훅을 사용해야 합니다.

훅 vs Composition API / 동기화 VS UI 생성

리액트 백그라운드에서 Vue3으로 넘어온 사람의 생각입니다.

물론 리액트는 react-as-a-ui-runtime의 멘탈 모델이 필요하고,

모든 것을 런타임에 의존하기 때문에, 성능 면에서는 뷰를 이길 수 없는 것이 사실이긴 합니다만,

리액트와 다르게 Vue3에는 개발자들을 실패의 구렁텅이로 빠뜨릴 수 있는 함정들이 꽤 많다고 생각합니다.

 

핵심 3종 API를 비교해 보겠습니다.

useState / useReducer Vs Ref

솔직히 말하면 저는 항상 함수를 이용해서 업데이트 해야하는 함수형 패러다임의 리액트를 선호합니다.

Ref는 쓰기 쉽다는 것 빼고는 크게 강점이 없어보입니다.

저는 또 리코일도 좋아하지 않기에 의존성을 깊게 숨기는 Computed를 써야 할 필요도 잘 못느끼겠구요.

특히 뷰를 사용하면 큰 컴포넌트를 만들어도 성능이 쉽게 최적화된다는 장점이 있는데,

저 Ref 때문에 디버깅이 어려워 질 수 있어서 반대로 컴포넌트를 잘게 쪼개야 한다고 생각하기도 합니다.

이렇게 되면 개발 속도 면에서 리액트랑 별 차이가 없으며,

유지보수성은 항상 뷰보다 리액트가 낫다는 평가를 받아왔기 때문에 뷰를 쓸 이유가 없습니다.

useEffect Vs watch(Effect)

useEffect는 2절에서 설명했듯이 다재다능합니다.

단지 마운트 / 언마운트 / 업데이트 시에 사이드이펙트를 UI와 어떻게 동기화 할것인지 묻습니다.

 

vue의 경우 (before/on)unmounted API와 watch, watchEffect가 용도가 분리되어 있는데,

이렇게 API가 많은 것의 단점은, API 용도가 섞여버릴 수 있다는 것이며, 이는 개발자들을 혼란스럽게 합니다.

useContext Vs Provide(Inject)

ContextAPI의 장점은 UI에서 데이터 의존성이 명시적으로 드러난다는 것입니다.

storybook과 같은 회사들은 컴포넌트 마저도 의존성으로 전달합니다.

 

vue3도 슬롯과 빈 템플릿을 이용해 해당 개념을 흉내낼 순 있겠지만,

Transition이나 Suspense 같은 것은 잘 만들어 놓고 왜 이건 이렇게 했는지 잘 모르겠습니다.

 

결론 : 뷰의 계절은 올까?

솔직히 말하면 뷰로 개발하면서 DX가 리액트보다 낫다는 생각은 들지 않았고,

react와 다르게 협업이 유리하다는 것도, 그냥 VAC 패턴으로 stateless하게 짜면 문제없을 뿐더러

성능도 nextjs, remix, 더 나아가 quik 같은게 나오는 판이라 크게 의미 없는 차이이며

생태계는 말도 안되게 차이나고,

에반 유는 뷰마스터리에서 자기 강의를 팔고있고

뷰에만 있지만 리엑트에 없어서 아쉬운 것이 딱히 없는것 같습니다?

 

물론 모두가 말하듯 뷰가 입문자 분들과 전문 프론트엔드 개발자가 아닌 분께 좋고,

script와 템플릿이 분리되어 있어서(물론 html 표준은 아니지만!) 협업이 좋다는 점은 어느정도 동의해야 겠네요.

그리고 무지성으로 대충 짜면 리액트가 더 꼴보기 싫은것도 팩트인것 같습니다.

 

추가로 아래와 같은 의견도 있긴 합니다.

 

무엇보다 의존성 배열과 useCallback같은 메모이제이션 API가 사람을 정말 피곤하게 한다는 건데요.

둘 다 명시적으로 읽는 사람한테 의존성을 보여준다는 장점도 있기 때문에 저는 살짝 동의하지 않긴 합니다만,

Context API와 셀렉터 같은 경우는 개선이 되면 좋겠다는 생각을 합니다.

리액트 코어 팀에서는 현재 컴파일러 프로젝트로 위의 여러 문제를 개선하려 하고 있다고 하네요.

https://velog.io/@eunbinn/react-i-love-you-but-youre-bringing-me-down

 

[번역] 리액트, 널 사랑하지만 넌 나를 슬프게 해

리액트에 느끼는 아쉬운 점들을 꽤 적나라하게, 하지만 또 재미있게 풀어낸 글입니다. 아쉬운 점을 나열했지만 제목처럼 리액트에 대한 애정이 담겨 있습니다 :) 원문의 마지막에 링크되어 있는

velog.io

참고 :

https://vuejs.org/api/composition-api-lifecycle.html

 

Composition API: Lifecycle Hooks | Vue.js

Less than 48 hours to get 45% off at Vue School Access 800+ lessons including the Vue.js 3 Masterclass

vuejs.org

 

반응형