IT일상

React와 Reactive는 어떻게 다를까요? 본문

프론트엔드

React와 Reactive는 어떻게 다를까요?

solo5star 2023. 8. 28. 05:21
리뉴얼 된 블로그로 보기: https://solo5star.dev/posts/53/

 

React와 Reactive? 뭔가 말장난 같습니다. 결론부터 말하자면, React는 Reactive 하지 않습니다. Reactive란 무엇이며 React는 그럼 어떻게 다른 걸까요? 한번 알아봅시다.

 

 

Reactive Programming

Reactive Programming은 무엇일까요? 설명하기 전에, 약간의 배경을 같이 알아봅시다.

 

바닐라 JS로 웹 사이트를 만들어보신 적이 있을까요? 위의 그림처럼 userCount 값을 1 더했을 때, 그 값이 UI에 반영되지 않을 것입니다.

 

변경된 값을 UI에 반영해주기 위해 직접 document.getElementById로 DOM 객체를 얻어 UI를 업데이트 해줬을 거에요. 이처럼 값의 변경과 UI를 변경해주는 코드가 한 세트로 같이 다니다 보면 복잡도가 2배, 3배는 우습게 증가할 것입니다.

 

여기서 Reactive Programming를 적용하여 문제를 해결할 수 있습니다.

 

reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. (위키백과, Reactive programming)

Reactive Programming은 (비동기) 데이터 스트림을 기반으로 하며, 값의 변경이 전파(propagation)되는 패러다임입니다. 데이터 스트림은 일단 제쳐두고, 우선은 변경의 전파에 집중하여 설명하겠습니다.

 

값을 변경하였을 때 변경이 자동으로 UI에 전파되도록 한다면, document.getElementById로 DOM을 가져와 직접 UI를 업데이트해주는 작업을 하지 않아도 됩니다.

 

그럼 어떻게 자동으로 전파하도록 구현할 수 있을까요?

const _state = { userCount: 0 };

const state = new Proxy(_state, {
  set(state, prop, value) {
    _state[prop] = value;
    document.getElementById('title-user-count').innerText = `현재 접속중인 사용자 수: ${value}`;
    return true;
  }
});
    <div>
      <h1 id="title-user-count"></h1>
      <button onclick="state.userCount += 1">사용자 수 추가</button>
    </div>

JavaScript의 Proxy를 사용하면 됩니다. Proxy 객체는 값의 읽기 또는 쓰기를 감지할 수 있는데, 쓰기를 할 때 UI를 업데이트하면 Reactivity하게 동작하도록 할 수 있습니다.

 

물론 Proxy를 사용하는 방법 외에도 변경을 감지하고 전파되도록 수 있다면 구현 방법은 얼마든지 다르게 할 수 있습니다. (대표적으로 옵저버 패턴이 있습니다)

 

Proxy를 사용하여 Reactive 구현

동작 예시는 Code Sandbox에서 확인할 수 있습니다: https://codesandbox.io/s/vanillajs-reactivity-using-proxy-zcg24l?file=/src/index.mjs 

 

 

Reactive: Vue.js의 Reactivity

유명한 프론트엔드 프레임워크 중 하나인 Vue.js에서 특징 중 하나로 Reactivity를 소개하고 있습니다.

One of Vue’s most distinctive features is the unobtrusive reactivity system. (Vue.js의 Reactivity in Depth)

즉 데이터를 변경하면 그 변경이 전파되는 모델을 가지고 있다는 뜻입니다.

<script setup>
import { reactive } from 'vue'

const state = reactive({ count: 0 });
</script>

<template>
  <button @click="state.count++">
    {{ state.count }}
  </button>
</template>

 

Vue.js에서는 Reactivity를 위해 reactive와 ref를 제공해주는데요, 이들은 실제로 JavaScript의 Proxy와 getter/setter를 사용하여 구현되어 있습니다. (https://vuejs.org/guide/extras/reactivity-in-depth.html#how-reactivity-works-in-vue)

 

Reactive에 근간을 두고 있기 때문에, 근본적으로 위에서 만들었던 예시랑 크게 다른 것은 없습니다.

 

 

React는... Reactive?

그렇다면 React는 Reactive하다고 할 수 있을까요?

const ExampleApp = () => {
  const [userCount, setUserCount] = useState(0);
  
  return (
    <div>
      <h1>현재 접속중인 사용자 수: {userCount}</h1>
      <button onClick={() => setUserCount(userCount + 1)}>사용자 수 추가</button>
    </div>
  );
}

setUserCount를 호출하면 userCount 값이 1씩 증가할 것이고, 변경된 값이 UI에 반영되어 표시될 것입니다.

 

하지만 setUserCount를 호출하였을 때 UI의 반영이 즉시 일어나지는 않습니다. react 공식 문서에도 setState는 UI에 바로 반영하는 것이 아니라, React에게 업데이트를 요청(Queueing)한다고 말합니다.

 

그럼 값의 변경은 언제 일어날까요? 전적으로 react 스케쥴러에 달렸다고 할 수 있습니다.

 

React는 값을 변경해달라는 요청(setUserCount)을 모아두었다가 한 번에 처리합니다. 따라서 setUserCount를 여러 번 호출하더라도 호출할 때 마다 UI에 반영되지 않습니다. 다음 렌더링에서 한꺼번에 UI에 반영됩니다.

 

업데이트 요청은 우선순위에 따라 반영이 늦어질 수도 있습니다. 이러한 사항은 모두 React 스케줄러가 결정합니다. 앞서 "전적으로 react 스케줄러에 달렸다" 라고 설명한 이유입니다.

 

Reactive와 다른 점을 여기서 발견할 수 있습니다. React는 값의 변경에 즉각 반응하지 않습니다. Reactive는 변경이 발생하면 바로 반응(Push)하지만, React는 변경들을 모아두었다가(Pull) 스케줄러가 적절한 시기에 UI에 반영해줍니다.

 

 

React는 Reactive 하지 않다

Reactive Programming으로 다시 돌아와봅시다. Reactive Programming은 비동기 데이터 스트림을 기반으로 합니다. 비동기 데이터 스트림이란 시간의 흐름에 따라 발생하는 데이터입니다.

* 사용자가 계속 버튼을 눌러 userCount를 변경
* 마우스를 계속 움직여 mousemove 이벤트가 지속적으로 발생
* 서버에서 읽지 않은 메일의 갯수를 지속적으로 전송

이러한 것들을 모두 비동기 데이터 스트림이라고 할 수 있습니다. Reactive Programming에서는 스트림에서 변경이 발생할 때 마다 변경을 관찰(observe)하고 반응할 수 있어야 합니다.

 

그러나 React는 변경을 관찰하고 반응할 수 있는 메커니즘을 제공하지 않고 React 스케줄러가 변경을 모아두었다가 한 번에 처리하기 때문에 reactive하다고 할 수 없습니다. (Pull Approach)

 

 

React vs Reactive

Reactive Programming은 React와는 달리, 적용 범위가 UI에만 국한되지 않습니다. 파일 다운로드, 비디오 재생, 라이브 채팅 등 비동기 데이터 스트림이라면 어디든 적용할 수 있습니다. Vue.js는 Reactive를 UI에 적용한 구현체라고 할 수 있습니다. 반면 React의 모델은 컴포넌트 렌더링에만 초점이 맞춰져 있습니다.

 

 

Push Approach vs Pull Approach

React는 Pull Approach를 채택하였기 때문에 변경을 모아두었다가 일괄 처리하는 등 React 스케줄러에서 연산을 최적화할 수 있습니다.

 

Push Approach를 사용하는 Vue.js의 경우 원치않는 연산이 발생하여 성능이 저하되기도 합니다. (https://vuejs.org/guide/best-practices/performance.html#reduce-reactivity-overhead-for-large-immutable-structures) 이러한 경우 불필요한 연산이 발생하지 않도록 개발자가 직접 최적화를 해주어야 합니다.

 

 

맺으며

처음엔 프로젝트에서 RxJS를 사용하기 위해 Reactive를 공부했지만 React와 Vue.js가 근본적으로 어떻게 다른지도 알아볼 수 있는 시간이 되었습니다. 

본문에서는 React와 Reactive를 상충하는 개념처럼 설명했지만 충분히 같이 쓸 수 있다고 생각합니다. 실제로 저도 React 프로젝트에서 터치 스와이프를 Reactive로 구현하기 위해 RxJS를 사용할 계획이 있기도 하구요.

혹시 React와 RxJS를 함께 사용하는 데에 관심있으시다면 React-RxJS를 한번 구경해보시면 좋을 것 같습니다.

 

 

참고한 글

* https://www.bigthinkcode.com/insights/reactivity-in-view-libraries

* https://velog.io/@janeshin059/React-Hooks-Vs.-RxJS-React%EB%8A%94-Fully-Reactive%ED%95%A0%EA%B9%8C

* https://react-rxjs.org/docs/core-concepts#push-vs-pull

* https://vuejs.org/guide/extras/reactivity-in-depth.html

* https://vuejs.org/guide/extras/rendering-mechanism.html#render-pipeline

* https://dev.to/this-is-learning/how-react-isn-t-reactive-and-why-you-shouldn-t-care-152m

Comments