[Vite+React+TS] React 프로젝트의 기본 구조와 흐름

타 프로그래밍 언어에 대한 경험은 있으나 JavaScript에 대한 지식과 경험이 전혀 없는 React 입문자가 빠르게 훑어보며 공부한 React 프로젝트 기본 템플릿의 구조와 흐름 정리.

들어가며

이번에 웹 애플리케이션 프로젝트를 시작하게 되면서 새로운 프로그래밍 언어를 공부해야 하는 상황이 되었다. 새로 접하게 된 것들은 TypeScript와 React, 그리고 Vite다. 그동안 다양한 프로그래밍 언어들을 거쳐온 경험을 바탕으로 디테일은 적당히 접어두고 빠르게 훑으며 적응해 보려고 한다. 자, 그럼 시작해 보자.

TypeScript란?

TypeScript
TypeScript

TypeScript란 JavaScript를 기반으로 하는 프로그래밍 언어이다. 따라서 둘 사이에 차이를 비교할 수 있으면 좋을 듯하여 각각의 특징을 정리해 보았다.

JavaScript 특징

  • 웹 개발에 주로 사용되는 스크립팅 언어
  • 애매한 객체 지향
  • 멀티 스레딩, 멀티 프로세싱이 미 지원 (하지만 방법이 없는 것은 아니라고 한다.)
  • 동적 타입 언어로 유연하고 자유도 높은 프로그래밍 가능

TypeScript 특징

  • JavaScript 기반의 컴파일 언어
  • JavaScript에서 객체 지향의 개념이 강화되어 주류 객체 지향 요소가 추가됨
  • 정적 타입 언어로 사전에 미리 점검 가능한 영역이 넓고, 코드 가독성이 높음

React란?

React.js
React.js

메타에서 페이스북 시절 오픈 소스로 공개한 프론트엔드 JavaScript 라이브러리다. UI를 컴포넌트 단위로 구성하여 재사용성이 높고 유지 보수 및 관리에도 용이하며, 가독성 측면에서도 장점을 보인다. 또, Virtual DOM을 사용하여 실제 DOM 내용 변경 시 필요한 부분만 업데이트할 수 있다. 이는 퍼포먼스 향상으로 나타난다.

Vite란?

Vite
Vite

웹 개발을 위해 개발용 서버(Dev server), 빌드 기능 등을 제공하는 개발 도구이다. HMR(Hot Module Replacement)을 지원하여 코드가 변경되었을 때 전체 페이지 새로 고침 없이 실시간으로 변경 사항을 반영할 수 있다. 또, 개발 서버의 성능이 좋은 편이다.

프로젝트 기본 구조

Vite, React, TypeScript 조합으로 신규 프로젝트 생성하면 다음과 같은 구조로 기본 템플릿이 만들어진다.

root

  • index.html: 해당 웹 애플리케이션이 실행될 때 시작점이 되는 HTML 파일.
  • package.json: 프로젝트 기본 정보와 미리 정의된 커맨드 키워드, 해당 프로젝트가 의존하는 확장 패키지들에 대한 정보가 담긴 파일.
  • package-lock.json: 프로젝트가 의존하는 확장 패키지들에 대해 좀 더 구체적인 조건 등을 명시하는 파일.
  • tsconfig.json: TypeScript 프로젝트의 컴파일 옵션 등을 설정하는 파일.
  • tsconfig.node.json: Node.js 환경에서 TypeScript 프로젝트의 컴파일 옵션 등을 설정하는 별도의 파일.
  • README.md: 프로젝트의 소개 정보 등을 입력할 수 있는 마크다운 문서 파일. GitHub로 push 하게 되면 원격 저장소 페이지의 소개 내용으로 반영할 수 있다.
  • vite.config.ts: Vite 프로젝트의 설정을 정의하는 TypeScript 파일.
  • .eslintrc.cjs: ESLint 설정을 정의하는 파일로 CommonJS 모듈 형식으로 작성.

root/node_modules

  • 프로젝트가 의존하는 확장 패키지들이 저장되는 폴더.

root/public

  • 프로젝트에 필요한 정적 파일들을 저장하는 용도로 사용되는 폴더. (예: 이미지, 폰트, JSON)
  • 해당 폴더의 파일들은 빌드 과정에서 변경되지 않고 빌드 후 루트 URL에 배치된다.

root/src

  • 애플리케이션의 핵심이 되는 소스 코드들이 저장되는 폴더.

root/src/assets

  • 애플리케이션에 사용될 각종 리소스들이 저장되는 폴더.

프로젝트 주요 소스 코드 분석과 흐름 파악

기본 템플릿 애플리케이션이 처리되는 흐름과 주요 소스 코드 파일들의 코드 분석 내용을 정리했다.

root/index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

위 소스 코드는 애플리케이션의 시작점이라고 할 수 있는 index.html 파일의 내용이다. 파일의 위치는 소제목에서 알 수 있듯이 프로젝트의 루트 폴더에 있다. 코드 내용을 보면 HTML에 익숙한 사람이라면 크게 어려운 내용은 없다. 하지만 5번과 11번 라인에 대해서는 약간의 추가적인 설명을 해 주면 좋을 것 같다.

우선 웹 페이지의 파비콘을 설정하는 5번 라인에서 href 속성 값으로 사용된 vite.svg 파일의 경로를 보면 루트임이 확인되는데, 프로젝트 루트 폴더에는 해당 파일이 없고, 대신 public 폴더에서 그 파일을 찾을 수 있다. 이는 프로젝트 빌드 시 URL 루트로 이동하게 된다.

11번 라인에서는 type 속성의 값으로 module이 할당되었다. 지정된 스크립트를 불러올 때 ES6(ECMAScript 2015) 표준을 따를 것임을 명시하는 것인데, ES6는 ECMA라는 국제기구에서 만든 ECMAScript의 6번째 개정판 문서이며, module로 지정이 되면 다음의 특징을 가지게 된다.

  • 스크립트 별로 개별 스코프를 가지게 되어 다른 스크립트에 동일한 변수명이 존재하더라도 충돌하지 않는다.
  • 스크립트에서 import/export 구문을 사용할 수 있다.
  • 비동기 로딩으로 적용되어 HTML 문서의 구문 분석 완료 후 스크립트가 실행되며, 이로 인해 페이지 로딩 성능이 향상된다.
  • 모듈 사용에 있어 CORS(Cross-Origin Resource Sharing) 정책이 적용된다.
  • 자동으로 Strict Mode가 적용된다.

CORS 정책은 브라우저가 한 출처에서 로드된 웹 페이지가 다른 출처의 리소스에 접근할 수 있도록 제어하는 보안 메커니즘이다. 다양한 출처 간의 리소스를 보다 안전하게 공유하기 위한 기술이라고 할 수 있다.

Strict Mode는 과거 JavaScript의 문법들 중 자유도를 높여주는 대신 오류 발생 확률이 잦았던 요소들을 사용할 수 없도록 제한하는 모드이다. 이를 사용함으로써 오류를 조기에 발견하고 코드 안정성을 높일 수 있다. 다음은 JavaScript 소스 코드 내에서 Strict Mode를 적용하는 코드인데, 이 내용을 포함하는 것이라 생각하면 된다.

use strict;

이제 script 태그에서 불러오고 있는 main.tsx 파일로 가보자.

root/src/main.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

1~4번 라인 import

  • 외부 요소를 main.tsx에서 사용할 수 있도록 해 주는 역할을 한다.
  • 1, 2번 라인에서는 확장 패키지의 요소들을 패키지 경로 입력을 통해 가져오고 있다.
  • 3, 4번 라인에서는 파일 경로 입력을 통해 가져오고 있다.
  • 4번 라인의 index.css 파일은 앞서 index.html에 적용될 스타일 시트이다.

6번 라인 document.getElementById('root')!

  • document는 JavaScript에서 웹 페이지 요소들에 접근할 수 있도록 제공하는 객체이다. 여기서는 index.html에 접근하는 용도라고 보면 된다.
  • getElementById('root')는 인자 root를 id로 하는 요소를 찾아준다. index.html을 보면 id 속성 값으로 root를 가지는 div 태그가 보이는데 바로 이 녀석을 반환해 주는 코드라고 보면 된다.
  • JavaScript에서 느낌표(!)는 논리적 부정을 나타내는 연산자이다. 이는 JavaScript를 기반으로 하는 TypeScript에서도 마찬가지이지만 TypeScript에서는 구문 뒤에 사용될 경우 단언 연산자라는 이름으로 다른 역할을 한다. 여기서는 앞 구문의 결과가 null이 아님을 단언하고 있다. null이 올리 없으니 컴파일 시 오류를 발생시키지 말라고 못을 박아 두는 것이라고 보면 된다.

6~10번 라인 ReactDOM.createRoot().render()

  • createRoot()는 지정된 인자를 React 애플리케이션의 루트로 설정한다.
  • render()는 인자로 지정된 React 요소들을 실제 DOM에 렌더링 한다.

7~9번 라인은 6번 라인의 render()의 인자로 전달된 React 요소들이다.

  • React.StrictMode 태그는 개발 환경에서만 활성화되는 태그로 deprecated된 요소들을 사용하거나 불필요한 렌더링을 할 때 경고를 준다.
  • App 태그는 3번 라인을 통해 가져온 태그이다.

그럼 App 태그의 실체를 확인하기 위해 이번에는 App.tsx 파일로 가보자.

root/src/App.tsx

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

1~4번 라인 import

  • 1번 라인처럼 import 다음에 중괄호({})를 사용하는 경우가 있다. 이것은 from 지정된 곳으로부터 특정한 부분(함수 혹은 값)만 가져오겠다는 것이다. 여기서는 react 패키지의 내용 중 useState만을 가져오겠다는 뜻이다. 이렇게 일부만 가져오게 되면 성능 향상과 가독성 개선의 장점이 있다.

6~33번 라인 function App() { ... }

  • App이라는 이름의 함수형 컴포넌트를 정의하고 있다.

7번 라인 const [count, setCount] = useState(0)

  • 대괄호([])는 JavaScript에서 배열을 나타낸다. 이 코드는 배열 비구조화 할당 문법으로 값을 초기화하고 있다.
  • const는 JavaScript에서 상수를 선언하는 키워드이다. 하지만 배열은 const로 선언해도 그 원소 값을 수정할 수 있는데, 이는 해당 배열을 참조하는 객체에게 할당되는 메모리가 constant 하다는 의미로 추정(?) 된다. 이와 같이 배열에 const를 사용하는 것은 일반적인 관행이라고 한다.
  • useState()는 앞서 1번 라인 import를 통해 가져온 요소이며, react의 훅(Hook) 중 하나이다. 함수형 컴포넌트의 상태(state)를 관리하는 데 사용되며, 함수의 인자로 상태의 초기값을 전달한다. 그 결과로 배열을 반환하는데, 배열의 첫 번째 원소는 상태 값, 두 번째 원소는 상태를 업데이트하는 setter 함수가 된다.

10~31번 라인

  • App 함수 호출 시 반환되는 내용이다. HTML 양식으로서 HTML 문법에 익숙하다면 전반적인 이해에 어려움은 없다.
  • 10, 31번 라인을 보면 반환되는 내용 전체를 이름 없는 태그가 감싸고 있는데, 이는 react의 프래그먼트라는 요소이다. 문법적으로 여러 개의 태그를 반환하는 것을 허용하지 않으므로 프래그먼트를 사용해 이들을 한데 묶어주어야 한다. <div> 태그를 사용해도 되지만 그렇게 되면 불필요한 태그가 하나 추가되는 것이다.
  • 13, 16, 21, 22번 라인에서 src 속성 값과 button 태그 값에 사용된 중괄호({})는 JSX 문법으로 HTML 중간에 JavaScript를 사용할 수 있도록 해주는 역할을 한다.
  • 13, 16, 20, 28번 라인에서 사용된 className 속성은 HTML 태그에서 클래스 지정 속성인 class와 같은 것이다. 굳이 className으로 바꾸어 사용하는 이유는 JavaScript의 class 키워드가 이미 다른 용도(객체 지향 개념에서는 클래스)로 사용하고 있기 때문이다.

21번 라인 () => setCount((count) => count + 1)

  • button 태그의 onClick 속성으로 사용된 이 JavaScript 코드를 화살표 함수라고 한다. 타 언어에서 익명 메서드나 람다식으로 알려진 개념과 유사한 것으로 함수명 등 생략 가능한 요소들을 최대한 생략 및 단순화하여 함수의 선언, 정의, 그리고 사용까지 한자리에서 진행하는 문법이다.
  • () => setCount() 그리고 setCount() 함수의 인자 (count) => count + 1이 각각 화살표 함수이다.
  • 화살표 함수는 (매개변수 1, 매개변수 2, ...) => { 함수의 본문 1; 함수의 본문 2; ... } 형식으로 사용되며, 함수의 본문이 한 줄일 경우 중괄호({})와 return을 생략할 수 있다.
  • setCount() 함수는 앞서 7번 라인에서 useState()로 생성된 상태 업데이트 함수이다. count + 1 대신 (count) => count + 1 형태의 함수형 인자를 사용한 것은 setCount() 함수가 비동기적 처리되기 때문에 아직 최신화되지 못한 상태 변수에 접근하는 경우를 방지하기 위해서이다.

35번 라인 export default App

  • App 컴포넌트를 다른 파일에서도 사용할 수 있도록 내보낸다.
  • default 키워드는 이 모듈에서 기본으로 내보내는 대상을 지정하는 데 사용하며, 한 모듈에서 여러 개의 default 지정은 불가능하다.
  • 만약 모듈에서 기본 내보내기를 지정하지 않으면 대신 여러 개의 네임드 내보내기가 가능해진다.

기본 템플릿의 실행 방법과 결과

실행 방법

React 프로젝트를 제대로 실행하기 위해서는 개발용 로컬 서버가 먼저 구동되고 있어야 한다. Vite의 역할 중 하나가 바로 그것인데, 맥의 터미널 같은 CLI 툴을 통해 다음 커맨드를 입력하면 개발용 서버를 시작할 수 있다.

npm run dev

프로젝트 루트 경로에서 입력을 한 뒤 다음과 같은 화면이 나타났을 때

Vite 개발용 서버 실행
Vite 개발용 서버 실행

[o + enter]를 입력하여 개발 중이던 프로젝트를 실행하면 된다. 실행된 프로젝트는 팝업 된 브라우저에 표시되며, 이때 CLI 툴로 돌아와서 [q + enter]를 입력하면 개발용 서버가 종료된다.

실행 결과

브라우저로 Vite + React + TS 프로젝트 기본 템플릿을 실행해 본 결과
브라우저로 Vite + React + TS 프로젝트 기본 템플릿을 실행해 본 결과

위 화면이 앞서 지금까지 살펴본 프로젝트 기본 템플릿의 실행 결과이다. 비교적 최근 언어들이라 그런지 Hello World 수준이 화려하기 짝이 없다.