똑같은 삽질은 2번 하지 말자

Vue와 React의 반응성 시스템의 차이 본문

카테고리 없음

Vue와 React의 반응성 시스템의 차이

곽빵 2023. 12. 1. 18:43

개요

React의 프로젝트를 진행하면서 컴포넌트의 리렌더링의 시점이 Vue와 꽤 차이가 있다는 걸 느끼면서 코드베이스로 어떤 차이가 있는지 조금 깊게 살펴보고 싶었기 때문에 이 글을 작성한다. (React는 함수형을 기준, Vue는 Vue3를 기준으로 한다.)

React의 반응성

React의 경우는 어찌보면 심플하다. useState 훅을 사용해 상태를 정의하고 그 상태를 set함수로 업데이트를 할 때 컴포넌트의 전체가 리렌더링이 된다. 이는 state가 Reactivity하다고 표현하는 건 살짝 이상할 수 있다.

import React, { useState } from 'react';

function Counter() {
  // useState를 사용하여 count 상태와 상태를 업데이트하는 함수를 선언
  const [count, setCount] = useState(0);

  // 버튼 클릭 시 count 상태를 증가시키는 함수
  const handleIncrement = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={handleIncrement}>
        Click me
      </button>
    </div>
  );
}

export default Counter;

 

물론, 전체가 리렌더링이 될 때 useCallback이나 useMemo 그리고 memo를 사용해 불 필요한 실행과 리렌더링을 피할 수 있는 방법도 존재한다. (참고로 useRef는 값이 변경되도 리렌더링 되지 않는다.)

Vue의 반응성

Vue의 경우는 React보다 조금 더 복잡하다. 보통 React에서 useState의 역할을 하는 친구가 Vue에서는 ref나 reactive가 되는데 이 친구들을 반응성 시스템은 React와 다르다.

어떻게 다른가?

Vue3에서는 Javascript의 객체의 get, set와 Proxy객체라는 친구를 이용해 반응성을 구현한다.

 

Proxy 객체란?

여기서 잠깐 Proxy 객체에 대해 잠깐 설명하자면, JavaScript의 Proxy 객체는 다른 객체에 대한 작업을 가로채고, 그 작업을 수정하거나 그 결과를 바꿀 수 있는 방법을 제공한다. 이를 통해 객체에 대한 읽기, 쓰기, 속성 열거 등의 기본적인 작업을 사용자 정의할 수 있다. Proxy 객체를 사용하면, 다음과 같은 작업을 할 수 있다.

  • 속성 접근 가로채기: 객체의 특정 속성에 접근할 때 마다 로그를 남기거나, 값의 유효성을 검증하는 등의 추가적인 작업을 수행할 수 있습니다.
  • 속성 변경 제어: 객체의 속성이 변경될 때 그 변경을 감지하거나, 특정 조건에서는 변경을 거부할 수 있습니다.
  • 객체의 기본 동작 수정: 객체의 기본적인 동작(: 속성 열거, 함수 호출 ) 사용자 정의 로직으로 수정할 있습니다.

실제로 어떻게 ref와 reactive가 반응성을 구현하고 있는 살펴보자.

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}

function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value')
    }
  }
  return refObject
}

 

이렇게 reactive는 Proxy객체를 이용해 반응성을 구현하고 ref는 객체의 get, set을 재정의해서 반응성을 구현한다.

여기서 반응성을 구현? 한다고 했는데 구체적으로 어떻게 반응성이 구현되는지는 track과 trigger함수를 살펴봐야 한다.

 

track Fucntion

let activeEffect // 나중에 등장하는 whenDepsChange에서 값이 업데이트 된다.

function track(target, key) {
  if (activeEffect) {
    const effects = getSubscribersForProperty(target, key)
    effects.add(activeEffect)
  }
}

 

trigger Function

function trigger(target, key) {
  const effects = getSubscribersForProperty(target, key)
  effects.forEach((effect) => effect())
}

 

whenDepsChange Function

function whenDepsChange(update) {
  const effect = () => {
    activeEffect = effect
    update()
    activeEffect = null
  }
  effect()
}
  • track: Vue의 반응성 시스템이 어떤 "의존성"(예: 반응형 객체의 속성)이 컴포넌트에 의해 사용되고 있는지 추적하는 역할을 한다. 예를 들어, 컴포넌트가 반응형 객체의 특정 속성을 렌더링할 때, track 함수가 해당 속성의 사용을 기록한다.
  • trigger: 추적된 의존성이 변경될 때 이를 감지하고 반응형 시스템에 알리는 역할(effects를 실행)을 합니다. 이는 의존성이 변경될 때 관련 컴포넌트를 재렌더링하도록 합니다.
  • effect: 특정 반응형 데이터에 의존하는 부수 효과이다. 조금 더 쉽게 말하자면 우리가 정의한 반응형 데이터(ref)가 변경 되었을 때 적절한 UI업데이트나 실행 되야 할 로직이 담겨있는 친구이다. effects라는 전역데이터 안에 저장되어 있다.
  • getSubscribersForProperty:  전역 데이터인 effects라는 데이터에 접근하거나 만약에 비어있으면 새로 생성할 때 이 함수를 사용한다. 
  • whenDepsChange: 의존성이 변경되었을 때 실행되는 함수나 로직을 정의한다 (여기서 위의 effects에 담기는 로직을 정의하는 것이다) 이는 Vue가 데이터의 변경을 감지하고, UI를 적절하게 업데이트할 수 있도록 하는 데 중요한 역할을 한다.

이제 vue의 반응성이 어떤식으로 동작하는지 어느정도 파악이 되었다. React와는 다르게 Vue는 우리가 선언한 종속성(반응성을 가진 데이터)들이 Reactivity하다고 표현할 수 있을 꺼 같다.

 

그럼 우리가 보통 template에서 반응성을 가진 데이터를 사용할 때 어떤식으로 리렌더링이 이루어 지는가를 구체적으로 살펴보자.

Vue의 컴포넌트와 태그 레벨에서의 리렌더링

Vue 3에서 ref로 선언된 변수를 template 안에서 사용할 때, Vue의 반응성 시스템은 자동으로 effect를 생성하여 해당 변수의 사용을 추적한다. 이 과정은 다음과 같다.

  • 템플릿 렌더링: 컴포넌트의 템플릿이 렌더링될 때, ref로 선언된 변수가 포함된 부분이 처리됩니다.
  • 의존성 추적: ref 변수가 읽힐 때, Vue의 반응성 시스템은 이를 감지하고 현재 렌더링 중인 컴포넌트를 해당 변수의 의존성 목록에 추가합니다.
  • Effect 등록: 이렇게 생성된 의존성 목록은 effect로 등록되며, ref 변수 값이 변경될 때마다 이 effect가 트리거되어 관련된 컴포넌트 부분이 다시 렌더링됩니다.

결과적으로, 템플릿 내에서 ref 변수를 사용하면 Vue 자동으로 반응성을 관리하여 데이터 변경에 따라 UI 업데이트한다.

 

여기서 Vue에서는 컴포넌트 레벨 뿐만 아니라 개별 태그나 요소 레벨에서도 반응성이 적용될 수 있다. 각각의 태그에서 반응형 데이터에 의존하고 있을 때 Vue의 가상 돔은 이러한 각 요소를 고유한 노드로 추적된다. 데이터가 변경될 때 Vue의 반응성 시스템은 해당 데이터의 의존하는 모든 요소를 식별하고 필요한 부분만 업데이트를 진행한다. 이는 같은 태그라도 서로 다른 데이터에 의존하는 경우 각각 별도로 처리될 수 있다는 말이다.

 

결론

React의 반응성 시스템을 보면 useState라는 훅을 이용해 상태를 정의하고 상태가 업데이트 됬을 때 그 상태를 사용하는 컴포넌트를 리 렌더링을 한다. 하지만 Vue에서는 ref, reactive를 통해 상태를 정의하고 이 상태를 사용하고 있는 컴포넌트나 태그, 요소레벨들을 의존성 목록으로 따로 관리함으로써 정확히 어떤 부분에 렌더링이 이루어져야 하는지를 파악할 수 있다. 결과적으로 Vue는 React보다 효율적으로 UI를 업데이트 할 수 있다.

 

Comments