React | 리액트 프로젝트의 기본 실행구조 분석
드디어 리액트 라이브러리를 익힐 때가 왔다!
처음 리액트를 구동시켜보고, 파일을 이리저리 훑어보는데 동작 원리가 궁금해졌다.
그래서 잠깐 리액트의 기본적인 실행구조를 파보는 시간을 가져보았다.
우선 리액트를 설치하면 다음과 같은 디렉토리와 파일들이 생성된다.
package.json 파일을 보니, 루트 디렉토리 가서 npm start 해보면 react가 실행될 것 같다.
그렇게 했더니 App.js 파일이 다음과 같이 성공적으로 실행이 됨을 확인할 수 있다.
자바스크립트 파일이 어떻게 <script> 태그 없이 index.html 에 로드되는걸까?
우선 현재 어플리케이션에서 렌더링되는 페이지는 index.html 이다.
그리고 App.js 에 가보면 html 코드같은 부분(JSX 문법)이 있는데, 여기에 수정을 가하면 렌더링되는 화면에도 변화가 생긴다.
- JSX(= JavaScript XML)는 HTML과 유사한 문법을 사용하는 JavaScript의 확장 문법이다. UI를 표현하는 리액트 컴포넌트들을 더 직관적으로 작성하기 위해 사용된다.
그러나 index.html 을 살펴보면, 어디에도 <script src="~/App.js"> </script> 와 같이 JS 파일을 로드하는 문장이 보이지 않는다.
그럼 대체 어떻게 App.js의 변화가 index.html에 반영되고 렌더링될 수 있는거란 말인가??
일단 리액트를 띄운 화면의 개발자 도구>Network 탭을 보면,
보이지도 않는 bundle.js 를 로드하고 있고, 그 안에는 webpack 이라는 키워드가 보인다.
우선 가동중이던 서버를 종료하고, npm run build 를 터미널에 입력해보자.
build 디렉토리가 생성되고, 여기서 빌드된 파일들을 확인할 수 있다.
이들은 우리가 작성하는 소스코드와 기계 중간쯤에 있는 파일이다. 즉, 사람이 읽으라고 있는 파일들은 아니다.
때문에 build 디렉토리 안에 있는 빌드된 버전의 index.html을 열어보면,
이렇게 한 줄로 쭉 나열되어 있는 것을 볼 수 있다.
이를 사람이 보기 좋게 공백을 다시 넣으면 다음과 같다.
엇?! 기존에는 없던 <script> 태그가 추가되어있다.
src가 가리키고 있는 /build/static/js의 main.d83d7c48.js 파일을 열어보면,
이렇게 생겼다. 정체가 무엇일까?
npm run build를 실행하면, 리액트 애플리케이션이 프로덕션 용도로 빌드된다.
이 과정에서 리액트 코드와 dependencies 들은 번들링되어 최적화된 자바스크립트 파일로 변환되는데, 그것이 해당 파일의 정체이다.
이후 빌드된 index.html 파일에서 이 번들된 자바스크립트 파일을 로드하고 있는 것이고,
결과적으로 리액트 애플리케이션의 모든 자바스크립트 코드와 라이브러리를 포함하는 주요 파일을 참조하고 있게 된다.
npx create-react-app 을 통해 리액트를 설치할 때 이를 보조하는 Webpack, Babel 등의 라이브러리도 자동으로 함께 설치되는데, 그중 Webpack이 번들러(모듈 번들링, 최적화 등)의 역할을 수행한다.
아마 위에서 본 bundle.js 와 webpack이라는 키워드는 이 내용 안에서, 더 내부적인 메커니즘에 의해 나온 결과일 것으로 생각된다.
이렇게 첫번째 궁금증인 '자바스크립트 파일이 어떻게 <script> 태그 없이 index.html 에 로드되는걸까?'는 해결되었다. 답은 '개발 모드에서는 보이지 않지만, 빌드 후에는 로드 부분이 추가된다. 그리고 Webpack 라이브러리가 번들러 역할을 하며, 번들된 js 파일을 index.html이 로드하고 있다.' 였다.
리액트 초기 화면이 렌더링되는 과정
(핵심 요약)
- 시작점 - index.js 파일:
- 리액트 프로젝트의 진입점은 일반적으로 src/index.js 파일이다. 이 파일에서 리액트 애플리케이션의 루트 컴포넌트가 DOM에 렌더링된다.
- index.js 파일에서 ReactDOM.createRoot 함수를 호출하고, document.getElementById('root')를 통해 index.html 파일 내의 <div id="root"></div> 요소를 찾는다. 이 요소는 React 애플리케이션의 마운트 포인트이다.
- 앱 렌더링 - index.js 파일, App.js 파일:
- root.render 메소드를 사용하여 React.StrictMode와 그 내부의 <App /> 컴포넌트를 렌더링한다. (React.StrictMode는 개발 모드에서 React 애플리케이션의 잠재적 문제를 감지하는 역할을 수행한다.)
- App 컴포넌트는 src/App.js 파일에서 정의된 컴포넌트이다. 이 컴포넌트는 애플리케이션의 사용자 인터페이스를 구성하는 주요 부분이다.
- App.js 내용의 렌더링 (index.html 과의 연결):
- App.js 파일은 함수형 컴포넌트인 App을 정의하고 있다. 이 컴포넌트는 JSX를 반환한다.
- App 컴포넌트 내에서 반환된 JSX는 실제 HTML로 변환되어 브라우저에 렌더링된다.
- 예를 들어, <div className="App">...</div>와 같은 JSX는 실제 HTML 요소로 변환되어 사용자에게 보여진다.
- 최종적으로, 사용자는 App.js에 정의된 내용을 index.html 페이지를 통해 볼 수 있다.
이 과정을 통해, 리액트는 index.js를 시작점으로 하여 App.js에 정의된 컴포넌트 구조를 index.html의 root 요소에 렌더링하고, 사용자에게 최종적인 인터페이스를 제공한다.
1. 시작점 - index.js 파일
다시 처음으로 돌아가서, 본격적으로 기본 실행 과정을 알아보자.
프로젝트 파일의 package.json에 들어가거 scripts 섹션을 보면, 우리가 npm start 할 때 start가 "react-scripts start" 라는 커맨드로 지정되어 있음을 확인할 수 있다.
검색해본 결과, 이 react-scripts 명령어에 대한 설명은 다음과 같다.
react-scripts start 명령어는 개발 모드에서 React 애플리케이션을 시작합니다. 이 스크립트는 내부적으로 Webpack 개발 서버를 구성하고 실행합니다. 이 서버는 소스 코드의 변경 사항을 실시간으로 감지하고, 핫 모듈 교체(Hot Module Replacement, HMR) 기능을 제공하여 개발자가 빠른 피드백을 받을 수 있게 합니다.
'react-scripts' 는 create-react-app에 포함된 스크립트 세트입니다. 이 스크립트들은 React 애플리케이션의 개발, 빌드, 테스트 및 배포를 위한 복잡한 설정과 프로세스를 추상화합니다. react-scripts를 사용하면, 개발자는 Webpack, Babel, ESLint 등의 복잡한 설정을 직접 관리할 필요 없이, React 애플리케이션 개발에 집중할 수 있습니다. 이 스크립트는 개발 환경을 간소화하고, 프로젝트 구성을 일관되게 유지하는 데 도움을 줍니다.
react-scripts가 실제로 어떤 파일을 진입점으로 사용하는지는 react-scripts 패키지의 내부 구성에 의해 결정됩니다. create-react-app을 사용하여 생성된 표준 React 프로젝트에서는, src/index.js가 기본 진입점으로 설정됩니다. 이 파일에서 React 애플리케이션의 루트 컴포넌트가 DOM에 마운트되며, 애플리케이션의 렌더링이 시작됩니다.
리액트 프로젝트의 기본 진입점은 index.js 파일임을 확인했다.
index.js 파일로 가보자.
여기에서 우선 ReactDOM 객체의 메서드인 createRoot를 호출한다.
- 리액트 애플리케이션의 루트 컨테이너를 생성한다.
Q. 루트 컨테이너? 루트 컴포넌트?
루트 컨테이너는 실제 DOM 요소로, index.js가 리액트 프로젝트의 진입점이라면, 루트 컨테이너는 리액트 애플리케이션의 진입점 역할을 수행한다. 여기서는 최상위 컴포넌트인 App 컴포넌트를 렌더링하는 역할을 한다.
루트 컴포넌트는 리액트 애플리케이션의 최상위 컴포넌트로, 기본 설정에서는 App 컴포넌트가 루트 컴포넌트 역할을 한다.
ReactDOM은 리액트의 가상 DOM을 의미하는 것이 아니다.
이름 때문에 헷갈릴 수 있지만, ReactDOM은 리액트의 Virtual DOM을 가리키는 것이 아니다. ReactDOM은 리액트와 실제 DOM 사이의 상호작용을 관리하는 라이브러리이다. 리액트와 DOM의 연결다리 역할이라고 봐도 될 듯 하다.
이는 리액트 애플리케이션을 웹 페이지의 DOM에 마운트하는 기능을 제공한다. 예를 들어, index.js에서도 볼 수 있는 ReactDOM.render() 메서드는 리액트 요소를 실제 DOM 노드에 렌더링할 때 사용된다.
document.getElementById('root')
- index.html에서 id = root인 요소를 찾아 인자로 넣어준다.
- 이는 방금 생성한 루트 컨테이너의 마운트 포인트로 root 요소를 지정하는 작업이다.
마운트: 리액트 컴포넌트가 처음으로 DOM에 렌더링될 때 이 첫번째 렌더링을 마운팅이라고 표현한다. 여기에는 1) 컴포넌트 생성, 2) 초기 렌더링, 3) 하위 컴포넌트의 생성 및 렌더링 등이 포함된다.
마운트 포인트: 리액트 컴포넌트가 실제 DOM에 삽입되는 지점
index.html의 이 빈 공간이 마운트 포인트가 되는 것이다.
그리고, 이 작업의 반환값을 root 라는 상수에 넣어준다.
2. 앱 렌더링 - index.js 파일, App.js 파일
그 다음으로, root.render 메소드를 사용하여 React.StrictMode와 그 내부의 <App /> 컴포넌트를 렌더링한다.
위 import 문을 보면 App 컴포넌트는 src/App.js 파일에서 정의된 컴포넌트임을 짐작할 수 있다. 이 컴포넌트는 최상위 컴포넌트, 즉 루트 컴포넌트이며 애플리케이션의 사용자 인터페이스를 구성하는 주요 부분이다.
root를 마운트 포인트로 치환해서 생각하면, 'App 컴포넌트를 실제 DOM에 렌더링 해주는 과정'이라고 볼 수 있다.
3. App.js 내용의 렌더링 (index.html 과의 연결)
App.js 파일은 함수형 컴포넌트인 App을 정의하고 있다. 이 컴포넌트는 JSX를 반환한다.
위 과정들을 통해 App 컴포넌트 내에서 반환된 JSX는 실제 HTML로 변환되어 브라우저에 렌더링된다.
최종적으로, 사용자는 App.js에 정의된 내용을 index.html 페이지를 통해 볼 수 있다.
이 전반적인 과정은 다음 그림으로 표현할 수 있다.
이로써 리액트 프로젝트에서 초기 세팅되는 주요 파일들의 상호작용을 대략적으로 이해하게 되었다 🥳