Table Of Contents
지금까지 리액트 앱을 생성하고 어떤 요소가 있는지 확인하고 실행까지 해보았습니다.
이번 절에서는 리액트 앱이 어떻게 동작하는지 그 원리를 좀 더 자세히 살펴보겠습니다.
리액트 앱에는 어떻게 접속하는 걸까?
앞서 create-react-app으로 리액트 앱을 만들고 npm run start 명령으로 앱을 구동해 보았습니다. 그 결과 리액트 앱을 실행하면 http://localhost:3000으로 접속한다는 사실을 알게 되었습니다.
그렇다면 어떤 원리로 리액트 앱에 접속하는 걸까요? 결론부터 말하자면 Create React App으로 만든 리액트 앱에는 웹 서버가 내장되어 있습니다. 즉, npm run start 명령을 실행하면 브라우저가 리액트 앱에 접속하도록 앱에 내장된 웹 서버가 동작합니다. 결국 내장된 웹 서버 주소로 브라우저가 자동으로 접속합니다.
웹 서버는 브라우저의 요청에 따라 필요한 웹 페이지를 보내주는 컴퓨터입니다.
예를 들어 네이버 웹 서버는 사람들이 접속할 수 있는 http://naver.com이라는 주소를 갖고 있습니다. 해당 주소로 접속 요청이 들어오면 웹 서버에서 네이버의 웹 페이지를 보내줍니다.
네이버 웹 서버에 접속하려면 https://naver.com이라는 주소를 입력하듯이 웹 서버에는 자신만의 주소가 있습니다. Create React App으로 생성한 리액트 앱의 주소는 기본적으로 http://localhost:3000으로 설정되어 있습니다. 그러므로 이 주소로 요청 해야 앞에서 생성한 리액트 앱에 접속할 수 있습니다.
그렇다면 localhost:3000이라는 주소는 어떤 의미일까요? 먼저 localhost는 내 컴퓨터의 주소를 가리킵니다. 따라서 localhost 주소로 무언가를 요청하면, 해당 요청은 여러분의 컴퓨터에 전달됩니다. 이것은 마치 우체국에 가서 여러분의 집 주소로 편지를 보내는 것과 같은 원리입니다.
localhost 뒤에 콜론(:)과 함께 나오는 3000은 포트(port) 번호입니다. 포트 번호는 컴퓨터에서 실행되고 있는 서버를 구분하는 번호입니다. 컴퓨터에는 기본적으로는 하나의 주소가 있는데, 이 주소로 요청을 받습니다. 그런데 컴퓨터에 여러 개의 서버가 실행되고 있다면, 요청을 받았을 때 어떤 서버에 대한 요청인지 모호할 수 있습니다. 따라서 서버별로 포트 번호를 정해놓으면, 해당 포트 번호에 대한 요청이 들어올 때만 응답하는 식으로 작업을 선별해 처리할 수 있습니다.
Create React App으로 만든 리액트 앱의 기본 포트 번호는 3000번입니다. 따라서 http://localhost:3000과 같이 localhost 3000번 포트의 서버로 접속을 요청해야 정상 적으로 리액트 앱에 접속할 수 있습니다.
결국 npm run start 명령으로 리액트 앱을 실행하면 내장된 웹 서버가 실행되면서 http://localhost:3000 주소 로 접속하게 됩니다.
리액트 앱의 동작 원리 상세 보기
리액트 앱을 실행하고 http://localhost:3000 주소로 접속하면 앞에서 살펴보았듯이 애니메이션처럼 움직이는 리액트 로고 페이지가 나옵니다. 이 페이지 하단에는 “Edit src/App.js and save to reload”라는 문장이 있는데, src 폴더의 App.js를 수정하고 저장하여 다시 로드하라는 뜻입니다.
비주얼 스튜디오 코드에서 src 폴더의 App.js를 열어보면 App 함수가 작성되어 있습니다. 이 함수의 return 문을 다음과 같이 수정하고 <Ctrl>+<S>로 저장합니다.
코드를 불러오는 중 입니다 ...이제 페이지의 렌더링 결과가 달라집니다. 함수 App처럼 HTML을 반환하는 자바스크립트 함수를 컴포넌트라고 했습니다. 컴포넌트는 이름과 함께 부르기 때문에 이제부터 App 컴포넌트라고 하겠습니다.
이처럼 페이지의 렌더링 결과가 달라진 이유는 무엇일까요? 이를 이해하려면 리액트 앱이 어떻게 동작하는지 알 필요가 있습니다.
사용자가 주소 http://localhost:3000으로 리액트 앱에 대한 서비스를 요청하면, 리액트 앱 서버는 우선 웹 페이지 파일인 public 폴더의 index.html을 보냅니다. 일반적으로 특정 웹 서비스에 접속하면 처음 만나는 페이지는 대체로 index.html 파일 입니다. 따라서 index.html을 열어 보면 왜 이런 결과가 나오는지 알 수 있습니다.
index.html을 열어 실제 페이지를 브라우저에 표시하는 <body> 태그를 확인하겠습니다.
코드를 불러오는 중 입니다 ...그러나 index.html의 <body> 태그에는 브라우저가 자바스크립트를 실행할 수 없을 때만 나타나는 <noscript> 태그와 id가 root인 빈 <div> 태그밖에 없습니다. 따라 서 index.html에는 페이지에 표시할 만한 요소가 하나도 없습니다.
뭔가 이상합니다. 분명 index.html에는 페이지에 표시할 만한 요소가 하나도 없는데, http://localhost:3000으로 리액트 앱에 접속하면 앞서 App 컴포넌트에서 수정했던 내용을 페이지가 표시하기 때문입니다.
이를 이해하려면 개발자 도구 [Element] 탭에서 <head> 태그에 작성된 <script defer src=".../bundle.js"> 태그를 확인해야 합니다. 개발자 도구의 [Element] 탭에서 이 태그를 찾아 클릭합니다.
<script>는 리액트 앱에 접속하면 자동으로 index.html에 추가되는 태그입니다. <script> 태그는 ‘/static/js/’ 경로에 있는 bundle.js라는 자바스크립트 파일을 불러와 실행합니다.
bundle은 꾸러미, 묶음이라는 뜻입니다. bundle.js는 src 폴더에 있는 자바스크립트 파일을 한
데 묶어놓은 파일입니다. 이렇게 여러 자바스크립트 파일을 하나로 묶는 작업을 번들링이라고 하며, 그 결과물인 bundle.js를 번들 파일이라고 합니다.
bundle.js는 src 폴더에 있는 index.js와 이 파일이 불러온 모듈을 하나로 묶어놓은 파일입니다. 결국 이 번들 파일은 index.js가 작성한 코드에 따라 동작합니다. 따라서 index.js에 어떤 내용이 있는지 살펴봐야 리액트 앱의 동작을 제대로 이해할 수 있습니다.
index.js에는 다음과 같은 코드가 작성되어 있습니다.
코드를 불러오는 중 입니다 ...index.js 파일에는 약 17줄의 코드가 작성되어 있습니다. 구체적으로 살펴보겠습니다.
먼저 살펴볼 곳은 import 문입니다.
코드를 불러오는 중 입니다 ...index.js에서는 import 문으로 App.js에 있는 App 컴포넌트를 포함해 여러 개의 모듈을 불러옵니다.
다음으로 살펴볼 부분은 ReactDom.createRoot 메서드입니다.
코드를 불러오는 중 입니다 ...ReactDOM.createRoot는 인수로 전달된 요소를 리액트 앱의 루트로 만들어 반환하는 메서드입니다. 여기서 루트란 뿌리라는 뜻이며, 트리 형태의 돔에서 자바스크립트 함수로 작성된 컴포넌트들의 루트 요소를 가리킵니다.
ReactDOM.createRoot 메서드에 인수로 document.getElementById('root')를 전달하는데, 이 메서드는 돔에서 id가 ‘root’인 요소를 찾아 반환합니다.
이 코드의 의미를 다시 정리하면 돔에서 id가 ‘root’인 요소를 루트로 만들어 root 라는 변수에 저장합니다. id가 root인 요소는 이미 앞에서 살펴보았습니다. 바로 public 폴더의 index.html에 있는 <div> 태그가 바로 루트 요소입니다.
계속해서 다음으로 살펴볼 부분은 ReactDom.createRoot 메서드 바로 아래에 위치한 root.render 메서드입니다.
코드를 불러오는 중 입니다 ...변수 root에는 현재 리액트의 루트가 저장되어 있습니다. render 메서드는 인수로 리액트 컴포넌트를 전달하는데, 이 컴포넌트를 돔 루트에 추가합니다. 따라서 render 메서드가 수행되면 전달된 리액트 컴포넌트가 돔에 추가되어 페이지에 나타납니다. 결론적으로 이 코드는 App 컴포넌트를 돔 루트에 추가하므로, 페이지에 App 컴포넌트에서 정의한 HTML 요소가 표시됩니다.
http://localhost:3000에서 개발자 도구를 열고, [Elements] 탭에서 리액트의 루트 요소로 사용된
<div id='root'>를 클릭하면, 해당 요소 아래에 App 컴포넌트가 반환하는 HTML 요소가 추가되어 있음을 확인할 수 있습니다.
지금까지 리액트 앱의 동작 원리를 살펴보았습니다. 리액트 앱의 동작 방식을 다시 한번 정리하면 다음과 같습니다.
- localhost:3000으로 접속을 요청하면 public 폴더의 Index.html을 반환합니다.
- index.html에는 src 폴더의 index.js와 해당 파일이 가져오는 자바스크립트 파일을 한데 묶어 놓은 bundle.js를 불러옵니다. <script> 태그에서 자동으로 추가합니다.
- bundle.js가 실행되어 index.js에서 작성한 코드가 실행됩니다.
- Index.js는 ReactDOM.createRoot 메서드로 돔에서 리액트 앱의 루트가 될 요소를 지정합니다.
- render 메서드를 사용해 돔의 루트 아래에 자식 컴포넌트를 추가합니다. 결과적으로 App 컴포넌트가 렌더링됩니다.
약간 복잡해 보이지만 리액트 앱의 기본 원리와 동작을 차근차근 살펴보았습니다.
지금은 모든 내용을 완전히 이해하지 못하더라도 큰 문제는 없습니다. 앞으로 진행할 예제들과 프로젝트를 구현하면서 리액트 앱의 동작 원리가 다시 궁금해지면 다시 한번 4장으로 돌아와 살펴보길 바랍니다.
리액트 앱의 기본 원리 그리고 동작 방식을 잘 몰라도 몇 가지 예제 정도만 배우면 간단한 리액트 앱을 만들 수 있습니다. 입문자 입장에서는 그런 방식이 더 빠르게 결과물을 만들어 낼 수 있으니 효율적이라고 생각할 수도 있습니다.
그러나 실력 있는 프론트엔드 개발자로 성장하려면 언제나 기초가 중요합니다. 실무에서는 상상하기 힘들 정도로 복잡한 리액트 앱을 직접 다뤄야 합니다. 이때 동작 원리를 제대로 이해하고 있지 못하다면 다양한 상황에 효율적으로 대처하기 어렵습니다. 또 새로운 기능이 공개되거나 추가되었을 때에도 학습 속도가 상대적으로 더디게 될 겁니다.