출처: react.dev

리액트

리액트(React)는 페이스북에서 개발된 JavaScript 라이브러리로, 사용자 인터페이스를 만들기 위한 것입니다. 주로 SPA(Single Page Application, 단일 페이지 애플리케이션)을 구축하는 데 사용되며, 재사용 가능한 UI 컴포넌트를 효과적으로 관리할 수 있도록 도와줍니다. 또한 리액트는 가상 DOM(Virtual DOM)을 사용하여 성능을 최적화하고, 데이터가 변경될 때 효율적으로 업데이트를 처리합니다.

 

SPA와 가상 DOM 및 DOM에 관해서 간략하게 소개드리자면, SPA는 페이지의 이동없이 동적으로 페이지의 콘텐츠를 업데이트하는 웹 애플리케이션입니다. 그리고 가상 DOM은 실제 DOM을 효율적으로 업데이트하여 성능을 향상시키는 기술입니다. Vue.js와 같은 다른 자바스크립트 라이브러리도 가상 DOM을 사용하고 있죠. 가상돔에 대한 더 자세한 내용은 글의 마지막에 다시 설명드리겠습니다.

특징

선언형 접근법과 JSX

리액트는 선언형 접근법(Declarative approach)을 사용하는데, 이는 만들어지는 최종 상태만을 정의하고 그러한 상태가 만들어지는 방법 혹은 규칙에 대한 정의는 하지 않는 것을 의미합니다. 여기서 말하는 최종 상태란, 한 가지 상태만을 의미하지 않고 다양한 조건에 따른 여러 상태를 의미합니다. 상태를 변경시키는 규칙을 정하는 명령형 프로그래밍(Imperative programming)과는 반대되는 개념이죠.

 

이렇게 들어서는 아직 감이 잡히지 않을 것입니다. 조금 더 구체적으로 말하자면, 기존의 자바스크립트는 화면에 요소를 그리기 위해서는 그 요소가 그려지는 방법을 코드로 정의하여야 했습니다. createElement(), getElementById(), append()등의 방법으로 새로운 내용이 그려지는 과정을 하나하나 컨트롤 해주어야했습니다. 하지만 리액트는 JSX를 도입하여 자바스크립트코드로 HTML과 거의 같은 문법을 사용(HTML문법과 거의 똑같이 생긴 자바스크립트 코드입니다!)할 수 있게 함으로, 요소가 그려진 결과를 보여주기만 하면 리액트가 자동으로 처리하여 화면에 요소를 그리게 되죠.

 

기존 코드

const content = document.createElement('p');
content.textContent = '새로운 내용';
document.getElementById('content-box').append(content)

리액트 JSX 사용

function App() {
  return (
    <div>
      <p>새로운 내용<p>
    </div>
  );
}

export default App;

위의 코드처럼 완성된 HTML의 모습을 보여주면, 리액트가 그 모양과 똑같이 자동으로 페이지를 그려주도록 하는 것이 JSX의 사용방식입니다. 이를 통해 DOM을 조작하는 복잡한 코드가 필요없어졌죠.

컴포넌트

위의 코드에서 export deafault라는 구문을 보셨나요? 리액트를 사용하면서 가장 많이 쓰게될 구문 중 하나입니다. 리액트는 컴포넌트들로만 이루어진 어플리케이션을 만듭니다. 컴포넌트는 사용자 인터페이스에 있는 재사용 가능한 구성요소이죠. 일반적인 프로그래밍에서 함수와 비슷하다고 생각하지면 됩니다. (실제로도 리액트의 컴포넌트는 함수로 작성됩니다. 객체(Class)로도 작성될 수 있지만 함수로 작성되는 것이 일반적이고 권장됩니다.)

출처: react.dev

리액트에서는 이러한 컴포넌트들이 조합되고 바꿔 끼워지면서 페이지를 만들고 변경할 수 있게 됩니다. 위의 사진에서 3개의 비디오를 표시하지만, 사실 같은 모양에 내용만 달라지는 것이죠. 리액트를 사용하는 개발자가 해야될 일은 필요한 컴포넌트들을 만들고 내용을 전달하여 최종적인 모양을 리액트에게 알려주는 것입니다. 컴포넌트를 관리하면서 반복을 피하고 코드를 작고 보기 편하게 유지할 수 있습니다. 각각의 컴포넌트를 하나의 기능을 수행할 수 있도록 만들고 각 페이지마다 컴포넌트를 불러와 기능을 사용하기만 하면 됩니다.

모든 것이 자바스크립트 코드

리액트의 코드는 모두 자바스크립트 코드로 이루어져 있습니다. HTML을 자바스크립트에서 사용할 수 있게 하는 JSX 덕분에 리액트는 자바스크립트코드만 작성하면 되죠. 리액트가 제공하는 함수들과 함께 자바스크립트 코드를 작성하면 리액트 프로젝트가 완성됩니다. 리액트 프로젝트의 수 많은 자바스크립트 코드 중 가장 먼저 실행되는 것이 바로 index.js입니다.

리액트 프로젝트

먼저 리액트 프로젝트의 src 폴더에 있는 index.js부터 시작합니다. 이 파일은 리액트 개발자가 조작하는 경우가 거의없는 파일이지만 페이지가 로딩될 때마다 가장 처음 실행되는 코드가 있습니다. 사실 정확하게는 index.js파일의 코드가 그대로 실행되지는 않고 리액트 프로젝트가 변환한 버전의 코드가 실행됩니다(모든 리액트 프로젝트에는 눈에 보이지 않지만 코드 변환을 위한 코드가 작성되어 있습니다.). 이를 통해 우리가 이제까지 알아본 컴포넌트, JSX가 인터넷 브라우저에서 실행가능한 자바스크립트 코드로 변환되고 실행됩니다. 이게 리액트에서 기존의 자바스크립트와 다른 방식으로 코드 작성을 가능하게 만드는 이유가 됩니다.

 

다시 index,js로 돌아와서 이 파일이 하는 역할이 무엇인지 알아볼까요?

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />)

위의 코드는 필요한 설명만을 위해 필수적이지 않는 내용은 삭제한 index.js의 소스코드입니다. 먼저 react에서 React를, react-dom에서 ReactDOM을 import하고 있습니다. 이 2개의 패키지는 리액트 라이브러리입니다. React와 react-dom이 합쳐져 리액트 라이브러를 구성하는 것이죠. 그리고 App은 루트 컴포넌트입니다. 리액트 프로젝트를 시작하면 자동으로 만들어지는 첫 번째 컴포넌트이고 개발자가 만드는 모든 컴포넌트는 최종적으로는 여기로 연결됩니다.

 

리액트에서는 자바스크립트 파일을 import할 때, 확장자를 생략해야합니다! 리액트가 자동으로 확장자를 붙여주기 때문이죠. 이 때문에 import './index.css';를 사용하면 CSS파일이 자바스크립트로써 import되죠(해당 CSS파일을 CSS파일이 아닌 자바스크립트 모듈로 처리합니다. CSS를 직접적으로 DOM에 주입하는 것이 아니라, JavaScript를 통해 스타일을 관리하고 처리하는 것이죠.).


그리고 ReactDom을 사용해 createRoot메서드를 호출하고 있습니다. 이 메서드는 개발자가 리액트로 만들려는 사용자 인터페이스의 기본 진입점을 생성하는 역할을 합니다. createRoot 안의 인자는 사용자 인터페이스가 로딩되었을 때, 웹페이지 상에서 배치되는 위치를 정하게 됩니다. 인자로 주어진 document.getElementById('root')의 기준이 되는 HTML 파일은 리액트 프로젝트의 puvlic 폴더 안에 있는 index,html입니다. 개발자가 다룰일이 거의 없는 파일이지만 브라우저가 로딩을 하는 HTML 파일이 바로 이 파일입니다. HTML 파일 하나, 즉 페이지가 하나로 구성되니 리액트는 SPA인 것이죠. 이 하나의 HTML 파일에서 모든 사용자 인터페이스와 웹페이지의 변경사항이 처리됩니다.

 

index.html

<body>
  <div id="root"></div>
</body>

index.html을 살펴보면, <body>에는 단 하나의 <div>태그가 있는데 이 태그의 id가 바로 root입니다. document.getElementById('root')통해 찾아오는 html element가 바로 이 div태그가 되는 것이죠. 리액트 프로젝트에서 만들게 될 모든 사용자 인터페이스가 바로 이 div 태그에 들어가게 됩니다.
이렇게 root에는 해당 div태그가 리액트의 기본 시작점으로써 할당됩니다. 그리고 render()메서드를 호출하여 렌더링할 것을 알려줍니다. 그러면 root에 할당된 div는 render메서드의 인자로 들어가는 컴포넌트로 대체됩니다.

왜 이렇게 만들었을까?

리액트는 가상 DOM을 이용하기 위해 이런 방식으로 만들어지게 되었다고 볼 수 있습니다. 그렇다면 가상 DOM은 무엇이고 왜 가상 DOM을 사용하려고 했을까요?

 

바닐라 자바스크립트를 사용해보신 경험이 있다면 아시겠지만, 브라우저의 DOM Selector API를 사용해서 특정 DOM을 선택하고 변화를 주려면 조건에 따른 수많은 규칙을 만들어야 합니다. 이러한 규칙은 페이지의 요소가 많아질수록 급속도로 복잡해져서 관리가 어려워지죠. 그래서 DOM의 업데이트를 간소화할 방법이 필요했습니다. 여기서 리액트는 새로운 발상을 하였는데, DOM이 바뀌면 어떻게 업데이트되는지에 대한 규칙을 만드는(명령형 프로그래밍) 대신 이전의 DOM을 없애고 새로 만들어진 결과로 바꿔서 보여줍니다(선언형 프로그래밍). 이 덕에 업데이트를 하는 방법에 대해서는 고민할 필요가 없죠. 코드가 간단해지는 겁니다.

 

출처 Medium, Virtual DOM (React) 핵심정리, momo

하지만 매번 모든 DOM을 새롭게 만든다면 페이지의 전환마다 새로운 페이지가 생성되므로 속도가 느려질 것입니다. 그래서 리액트는 가상 DOM이라는 것을 만들었습니다. 가상 DOM은 이름처럼 실제 DOM이 아니라 자바스크립트의 객체로써 존재합니다. 그래서 가상 DOM을 조작하거나 만드는 것은 실제로 렌더링이 되지 않는 등의 측면에서 실제 DOM을 조작하고 만드는 것보다 훨씬 빠르죠. 리액트는 상태가 업데이트되면 먼저 가상 DOM을 만들고 실제 DOM과 다른 부분을 찾아 해당하는 부분만을 다시 그립니다. 이때, 다른 부분을 한 번에 다시 그리죠. 리액트는 필요한 부분만 한 번에 다시 그리는 방식을 통해 빠른 속도를 보장하였고, 동시에 개발자가 실제 DOM에 접근할 필요가 없게 만들어주어 관리를 더 쉽게 만들어줍니다. 요약하자면, 리액트가 가상 DOM을 사용하는 이유는 DOM의 빠른 업데이트 뿐만 아니라, 선언형 접근법을 유지하기 위해서 입니다.

참고자료

React(react.dev), createRoot
React(legacy.ratecjs.org), Virtual DOM과 Internals
Medium, momo, Virtual DOM (React) 핵심정리
벨로퍼트 [번역] 리액트에 대해서 그 누구도 제대로 설명하기 어려운 것 – 왜 Virtual DOM 인가?

+ Recent posts