왜 리액트를 쓰나요?

이 글은 Learn you a React for good 시리즈의 일부입니다.

새로운 것을 배울 때에는 무엇을 배우든, 배우기 이전에 그 용도와 사용하는 이유에 대해서 알아야 합니다.
용도를 모르면 알맞는 상황에서 사용할 수 없고, 사용하는 이유를 모르면 안 쓰느니만 못 하기 때문입니다.

리액트의 용도는 리액트의 공식 홈페이지에 아주 큰 글씨로 써져 있습니다.

리액트의 용도

네, 리액트는 UI(유저 인터페이스)를 만들기 위한 자바스크립트 라이브러리입니다.

NOTE:

리액트를 라이브러리라 불러야 할 지, 프레임워크라 불러야 할 지에 대한 논쟁은 과거부터 이어져오는 꽤나 큰 떡밥입니다. 저는 제작사인 FB 측의 주장대로, 라이브러리라 부르도록 하겠습니다.

그렇다면, 리액트는 왜 쓰는걸까요?
UI를 만들기 위한 라이브러리는 리액트만 존재하는 게 아닌 데 말이죠.
여러 가지의 이유가 있겠지만, 이 글에서는 가장 대표적인 세 가지 이유를 알아보도록 하겠습니다.

강력한 추상화 도구, 컴포넌트

리액트로 만들어진 어플리케이션은 기본적으로 엘리먼트(Element)라는 화면에 보여질 것을 표현하는 요소로 구성되어 있습니다.
객체지향 프로그래밍 언어(OO Language)로 만들어진 프로그램이 객체로 구성되어 있는 것과 같이요.

엥, 그럼 컴포넌트는 뭐냐고요?
너무 성급해 하지 마세요. 천천히 하자구요.
객체지향 프로그래밍 언어에는 생성자가 있습니다. 객체를 생성하고 초기화하는 역할을 맡은 요소지요.
마찬가지로, 리액트에는 리액트 엘리먼트를 생성하는 컴포넌트(Component)가 있습니다.
컴포넌트는 props라는 입력을 받고, 리액트 엘리먼트를 만들어냅니다.
즉, 어찌 되든 리액트 엘리먼트를 만들어 내기만 하면 되기 때문에 함수로도(함수 컴포넌트) 그리고 클래스(클래스 컴포넌트)로도 컴포넌트를 정의할 수 있습니다.

NOTE:

사실 리액트 어플리케이션은 노드라는 리액트 엘리먼트의 상위 개념인 요소로 이루어져 있습니다. 컴포넌트 또한 노드를 결과로 생성하고요.
하지만 리액트 노드가 무엇인지에 대해 정확하게 설명해드리려면 이후 소개해드릴 개념들까지 전부 끌어와야 하기 때문에, 지금은 리액트 엘리먼트를 대신 사용하도록 하겠습니다.
여담으로, 실제로 모든 리액트 엘리먼트는 리액트 노드이기도 합니다.

좋아요, 이제 컴포넌트가 뭔지 감을 잡으셨을겁니다.
그럼 지금부터는 컴포넌트가 왜 중요한 개념인지 알아보겠습니다.

먼저 결론부터 말씀드리자면, 컴포넌트는 복잡한 UI단을 추상화 해준다는 점에서 중요합니다.
그렇다면, 도대체 왜 추상화를 해준다는 점이 중요할까요? 어려운 질문이지만 지금부터 그 이유를 알아가도록 하겠습니다.
컴퓨터를 이용해 해결하는 문제들은 복잡한 문제들입니다.
(만약 간단한 문제였다면 컴퓨터를 쓸 이유조차 없었겠죠.)
그럼 복잡한 문제들을 어떻게 해결할까요?
아시다시피, 컴퓨터로 문제를 해결하기 위한 방법을 제시하는 방법론을 프로그래밍 패러다임이라 부릅니다.
즉, 프로그래밍 패러다임들은 앞에서 이야기한 복잡한 문제들을 해결하는 방법을 제시하고 있습니다.
모든 프로그래밍 패러다임의 해답을 볼 수는 없으니 잘 알려진 프로그래밍 패러다임인 객체지향(Object-Oriented), 명령중심(혹은 절차지향, Procedural), 함수형(Functional)의 해답을 살펴보도록 하겠습니다.
흥미롭게도, 같은 언어를 사용하여 작성한다고 하더라도 프로그램의 구조가 극과 극을 달릴 정도로 다르게 되는 이 세 패러다임이 복잡한 문제를 해결하는 방법의 기초적인 아이디어는 전부 같습니다.
세 패러다임 모두 각자의 개념을 통해 문제를 작게 쪼개고, 해결한 뒤, 그 해결법을 다시 합쳐 복잡한 문제를 해결합니다.

프로그래밍 패러다임 문제를 쪼개기 위한 개념
객체지향 객체
명령중심 프로시저,서브루틴
함수형 함수

그럼 왜 프로그래밍 패러다임들은 왜 문제를 한번에 풀지 않고 쪼개서 풀까요?
컴퓨터는 복잡한 문제를 한번에 해결할 수 없는 걸까요?
제가 좋아하는 프로그래머인 Bartosz Milewski에 따르면, 아닙니다.
프로그래밍 패러다임들이 복잡한 문제를 쪼개서 푸는 아이디어에 기반한 이유는 사람의 뇌의 한계 때문입니다.
아무리 컴퓨터가 복잡한 문제를 한번에 해결할 수 있다고 하여도, 그 컴퓨터에게 명령을 내리는(컴퓨터는 명령에 따라 일을 할 뿐, 스스로 무언가를 하지 않습니다.) 사람이 그런 방법을 설계해 낼 수 없으면 컴퓨터는 무용지물이 되어버립니다.
그리고 앞에서도 말씀드렸듯이, 사람은 복잡한 문제를 한번에 해결해 내는 방법을 설계할 수 없습니다.
그럼 컴퓨터는 쓸모없는 물건인걸까요? 우리는 지금까지 의미없는 일을 한 걸까요? 지금까지 만들어 온 프로그램들은 컴퓨터가 필요없을 정도로 쉬운 문제들을 해결하는 프로그램들이였던걸까요?
전혀 아닙니다.
분명 우리는 지금까지 복잡한 문제를 해결하는 방법을 설계해 왔습니다.
어떻게 한 걸까요? 초능력이라도 발휘된걸까요?
아쉽게도, 초능력은 아니지만 초능력에 비견될 만한 인간의 능력을 통해 설계해온 것입니다.
어떤 능력이냐고요? 바로 상상력 입니다.
프로그래밍 패러다임들은 문제를 각자의 개념을 통해 쪼갠다고 하였습니다.
왜 굳이 각자의 개념을 통해 쪼갤까요? 그냥 쪼개면 안 되는걸까요?
프로그래밍 패러다임들이 문제를 쪼개기 위한 자신만의 개념을 가진 이유는 간단합니다. 바로 쪼개어 해결한 문제를 푸는 법을 숨기기 위해서입니다.
왜 기껏 해결책을 찾아놓고 다시 숨길까요?
간단합니다, 프로그래머가 신경쓰지 않게 하기 위해서입니다.
앞서 이야기했듯이, 사람의 뇌의 정보 저장 능력 및 처리 능력은 한계가 있습니다.
아무리 문제를 쪼개었다고 하여도, 쪼개진 문제의 해결책을 다른 쪼개진 문제들의 해결책과 합치기 위해서는 필히 뇌에 해결책을 저장해야 합니다.
이렇게 저장하다 보면 사람의 뇌는 한계에 도달하게 되어 있지요.
하지만, 그 해결책을 숨긴다면 어떨까요?
프로그래머는 그저 어떤 상자 안에 해결책이 들어 있다고 상상하기만 하면 되는겁니다. 안에 문제를 넣으면 그 문제에 대한 답을 다시 던져주는 상자요!
이렇게 문제를 추상화시키면 이론상 모든 문제를 뇌에 저장할 수 있게 됩니다.
즉, 해결책을 무한대로 합쳐나갈 수 있다는 이야기입니다!
다시 처음으로 돌아가 결론을 내리자면, 추상화는 사람의 뇌로는 설계할 수 없는 복잡한 문제에 대한 해결책을 설계할 수 있게 해 주기 때문에 중요합니다.

다시말해, 리액트의 컴포넌트는 프로그래밍 패러다임의 복잡한 문제를 쪼개기 위한 개념 같은 존재로, 복잡하고 거대한 UI도 쉽게 설계할 수 있게 해 주기 때문에 중요합니다.

옵져버블 없는 리액티브 프로그래밍

UI는 사용자와 끊임없이 상호작용합니다.
사용자의 입력에 따라 어플리케이션의 상태가 변화하고, 변화된 상태에 따라 화면이 변화합니다.
상태와 화면, 이 둘은 변화하는 값(Value over time)입니다.

변화하는 값들은 다루기 어렵습니다.
값의 변화에 맞추에 관련된 다른 것들도 변화시키기 위해 항상 예의주시해야 하기 때문입니다.
하지만 사람들은 여기서 포기하지 않고, “값의 변화를 스스로 전파시키게 하면 된다.” 라는 아이디어에 기반해 리액티브 프로그래밍이라는 프로그래밍 패러다임을 고안해 냈습니다.
리액티브 프로그래밍(Reactive Programming)은 옵져버블(Observable)과 여러 개념들을 사용해 변화하는 값들을 훌륭히 다룹니다.
하나의 예시를 보여드리자면, 이렇게요.

NOTE:

옵져버블, 스트림(Stream). 이 두 용어는 동의어입니다. 리액티브 프로그래밍 커뮤니티는 어떤 단어를 쓰든 같은 의미라고 보지요.

1
2
3
4
5
6
7
8
import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';

const inputElement = document.getElementById('textInput');
const input$ = fromEvent(inputElement, 'change'); // inputElement에서 'change' 이벤트가 발생할 때 마다 그 이벤트를 자신의 구독자(subscriber)에게 전달하는 옵져버블을 만듭니다.
const text$ = input$.pipe(map(event => event.target.value)); // input$가 값을 전달할 때 마다 주어진 함수를 이용해 그 값을 변형시킨 후 다시 전달하는 옵져버블을 만듭니다.

text$.subscribe(console.log); // text$ 옵져버블이 전달한 값을 출력합니다.

짜잔, 리액티브 프로그래밍을 하면 이렇게 데이터 플로우를 선언하여 비동기, 동기 할 것 없이 변화하는 값들을 쉽게 다룰 수 있습니다.
듣기에는 정말 좋아 보이지만, 리액티브 프로그래밍에도 몇몇 문제가 있습니다.
먼저, 가장 큰 문제는 프로그래머가 옵져버블 간의 구독(subscription)에 많이 신경써야 한다는 점 입니다.
시그널(singal) 옵져버블이 아닌 이벤트(event) 옵져버블 들은 구독 되는 시점이 중요하고, 그 옵져버블이 어떤 종류(hot/cold/warm)의 옵져버블이냐에 따라 구독이 만들어내는 결과가 달라지기 때문입니다.
이는 옵져버블과 옵져버블의 주위 환경을 프로그래머가 다룸으로서 생기는 문제로, 리액트는 옵져버블과 옵져버블의 주위 환경을 감춤으로서 이 문제를 해결했습니다.
즉, 간단하게 표현하면 리액트는 다음과 같이 구성된 것 입니다.

1
2
3
4
5
6
import React from 'react';

function MyComponent() {} // 여기에 우리가 작성한 멋진 컴포넌트가 있다고 상상하세요.

const element$ = props$.map(MyComponent) // props 들의 옵져버블이 props를 전달할 때 마다 그 props를 가지고 컴포넌트를 호출합니다. 결과적으로 props들의 옵져버블을 리액트 엘리먼트들의 옵져버블로 만듭니다.
element$.subscribe(React.renderReactElement); // 리액트 엘리먼트들의 옵져버블을 구독해, 리액트 엘리먼트들이 전달될 때 마다 화면을 새로 랜더링합니다.

우리의 컴포넌트는 옵져버블이니 구독이니, 복잡한 개념들을 생각하지 않고 주어진 데이터를 가지고 어떻게 랜더링할 것인가만 생각하면 됩니다.
왜냐하면 리액티브 프로그래밍과 관련된 복잡한 일들은 리액트가 전부 대신 해주기 때문이죠!

다음으로, 또 다른 문제는 학습 비용(Learning curve)입니다.
옵져버블, 오퍼레이터, 구독, 시그널/이벤트, cold/hot 옵져버블 등 많은 개념으로 이루어져 있고, 이 개념들을 정확하게 알지 못하면 코드가 돌아가지조차 않는 상황이 발생하기 때문에 리액티브 프로그래밍의 학습 비용은 상당하다고 할 수 있습니다(적어도 다른 프로그래밍 패러다임은 돌아가기는 돌아갑니다. 구조가 이상하게 뽑혀 나오거나 효과를 발휘하지 못하는 상황에 빠지게 되어서 문제지만요.).
하지만 리액트는 저 모든 걸 리액트 안으로 숨겨 버림으로서 사용자가 리액티브 프로그래밍에 대해 무지하여도 문제가 없도록 하였습니다.

정리하자면, 리액트는 리액티브 프로그래밍을 함으로서 얻을 수 있는 이득을 거의 공짜로 제공해 줍니다.
어떤 이득이냐고요? 변화하는 값을 선언적인 방법으로 손쉽게 다루는 것이요! 결과적으로 여러분은 사용자와 상호작용하는 UI(Interactive UI)를 정말 간단하게 만들 수 있는 것입니다!

가상 돔을 통한 빠른 랜더링

자, 마지막은 아마 여러분들이 잘 아실만한 이야기입니다.
네, 바로 가상 돔(Virtual DOM, VDOM)입니다.

아까 제가 컴포넌트에 대한 이야기를 해드릴 때, 이런 말을 했었지요.

리액트로 만들어진 어플리케이션은 기본적으로 엘리먼트(Element)라는 화면에 보여질 것을 표현하는 요소로 구성되어 있습니다.

무언가 비슷한 것이 떠오르지 않으시나요?
네, 바로 문서 객체 모델(Document Object Model, DOM) 입니다.
DOM은 현재 문서를 DOM 노드(Node)들의 트리(Tree)로 표현하지요.

왜 리액트는 DOM 노드를 사용하지 않고, 자체적인 엘리먼트들을 통해 화면을 구성할까요?
사실, 리액트는 DOM을 내부적으로 사용합니다. 그러나 우리에게는 노출시켜주지 않는 것일 뿐 입니다.
왜냐하면 DOM은 변경에 민감하고, 어플리케이션의 성능에 큰 영향을 주기 때문에 직접 다루는 일은 위험하기 때문이죠.
그걸 알고 있는 리액트는 리액트 엘리먼트로 논리적인, 가상의 DOM 트리를 프로그래머가 다루게 하고 자신은 프로그래머가 만들어낸 가상의 DOM 트리를 분석하여 DOM 트리를 정교하고 똑똑하게(필요한 부분만 업데이트 하거나, 당장 필요하지 않은 업데이트는 미루고, 변경 작업을 스케쥴링 하는 등 여러 최적화를 합니다) 다루지요.

즉, 리액트는 개발자가 신경쓰기 힘든 사소한 부분들을 직접 해결해주어 개발자가 어플리케이션 개발에 집중할 수 있게 해줍니다. 개발자들은 데이터를 다루고 화면을 구성하기 위해 고용되었지, DOM 트리랑 최적화 싸움 하라고 고용된 건 아니니까요(고용주 입장에서요, 최적화도 물론 중요하지만 프로덕트가 만들어지는 게 더 중요하지요.).


자, 지금까지 리액트를 사용하는 이유들에 대해 알아보았습니다.
이 외에도 몇몇 이유가 더 있지만 이 정도로도 충분히 동기를 부여해드렸다고 생각하기 때문에 굳이 다루지 않도록 하겠습니다.

그럼 저는 다음 글인 새로운 문법을 소개합니다!로 다시 찾아뵙도록 하겠습니다.
좋은 하루 되세요 :)))

공유하기