⚛️ 01. React 렌더링 생존기 — 왜 CRA를 피해야 하는가?

📋 개요

CRA가 아닌 Vite로 시작하는 이유, 가상돔의 본질, 그리고 JSX의 진짜 모습을 파헤칩니다.

🎯 이 섹션을 읽고 나면:

  • React 프로젝트를 세팅할 때 왜 더 이상 CRA(Create React App)를 쓰지 않는지 설명할 수 있다.
  • 바닐라 JS로 짠 코드와 React의 렌더링 메커니즘 차이를 멘탈 모델로 그릴 수 있다.
  • HTML처럼 생긴 JSX가 브라우저에 도달하기 전 어떤 마법을 거치는지 정확히 이해한다.

📋 목차


📌 이 문서를 읽기 전에

⏱️ 예상 읽기 시간: 15분 / 핵심 파트만: 10분

🗺️ 이 문서의 배경 세계관: '영수네 커뮤니티'
이 가이드는 '개발자 스터디 매칭 커뮤니티'를 만드는 가상의 팀 이야기를 다뤄.

  • 영수: PM 겸 백엔드 리드. "빨리 출시하자!"
  • 영호: 5년 차 프론트엔드 리드. "원리를 모르면 결국 나중에 터집니다."
  • 영철: 열정 넘치는 신입 프론트엔드. 매번 순진한 코드(Naive Code)를 짜고 영호에게 배운다.
  • 영숙: 디자이너. "여기에 팝업 하나만 더 띄워줄 수 있죠?"

🤔 1. 우리는 왜 React를 시작해야 하는가?

어느 날, 커뮤니티 프로젝트 기획 회의.
영수(BE): "영철아, 화면에 게시판 리스트 띄우고 좋아요 버튼 누르면 하트 색깔 칠해지는 거 있지? 그냥 jQuery나 순수 자바스크립트로 쓱쓱 짜면 안 돼? 빠르잖아."

🤔 잠깐, 먼저 생각해봐
만약 바닐라 JS로 "좋아요 버튼을 누르면 하트가 빨갛게 변하고 텍스트가 '좋아요 취소'로 바뀌는" 코드를 짠다면 몇 줄이 필요할까?

영철(신입): "어... DOM 요소 찾고, 클래스 이름 바꾸고, 텍스트 노드 수정하고..."

// ❌ 순진한 바닐라 JS (Naive Approach)
const likeBtn = document.getElementById('like-btn');
const heartIcon = document.getElementById('heart-icon');
 
likeBtn.addEventListener('click', () => {
  if (heartIcon.classList.contains('red')) {
    heartIcon.classList.remove('red');
    likeBtn.innerText = '좋아요';
  } else {
    heartIcon.classList.add('red');
    likeBtn.innerText = '좋아요 취소';
  }
});

이 코드는 당장은 잘 작동합니다. 하지만 상태(State)가 늘어나면 어떨까요?
좋아요가 눌렸을 때 옆에 있는 '인기글 배지'가 나타나야 하고, '알림 아이콘'에 빨간 불이 들어와야 한다면? 개발자는 어느 순간부터 UI를 그리는 게 아니라 "DOM의 상태를 일일이 추적하고 수동으로 찌르는(Mutate)" 유지보수 폭탄을 떠안게 됩니다.

React가 해결한 본질 (선언적 UI):
React는 "과정을 생략하고 결과만 말해" 라는 철학을 가졌어.
"버튼 텍스트를 A로 바꾸고 색상을 B로 바꿔라" 라고 명령(Imperative) 하는 대신, "좋아요가 눌린 상태 화면은 이거야" 라고 미리 선언(Declarative) 해두면, React가 알아서 화면을 그 상태로 동기화시켜 주는 거지.


🏗️ 2. Vite + TypeScript: 5년 차의 시작법

영철(신입): "아하! 그럼 얼른 구글에 '리액트 시작하기' 검색해서 npx create-react-app my-app 치겠습니다!"
영호(리드): "잠깐! 그거 쓰면 유지보수 지옥갑니다. 우린 Vite 쓸 거예요."

왜 CRA(Create React App)는 죽었는가?

이전까지 React의 표준 시작법은 CRA였어. 하지만 CRA는 Webpack이라는 구형 번들러를 기반으로 해. 소스 코드가 100줄이든 1만 줄이든, 파일을 수정할 때마다 앱 전체를 다시 빌드 해서 브라우저에 쏴줘야 했지. 매번 코드 수정하고 10초씩 기다리는 끔찍한 경험이었어.

구원자 Vite (프랑스어로 '빠르다'는 뜻)

Vite는 브라우저가 기본적으로 지원하는 ES Modules 기능을 이용해. 파일이 1만 개 있어도, 네가 방금 수정한 그 파일 딱 하나만 브라우저로 날려버려. 수정 사항이 0.1초 만에 화면에 반영되지.

# ✅ 5년 차의 완벽한 시작 명령어 (Vite + TypeScript)
npm create vite@latest 커뮤니티앱 -- --template react-ts

📖 용어: TypeScript(TS)
JS에 '타입(Type)'이라는 안전벨트를 채운 언어. "이 변수에는 문자가 아니라 숫자만 들어갈 수 있어!"라고 미리 정해두면, 영철이가 실수로 문자를 넣었을 때 에디터에서 빨간색 밑줄로 즉시 경고해줘. 이거 없이 큰 프로젝트를 하는 건 눈 감고 운전하는 것과 같아.


🧩 3. 가상 돔(Virtual DOM): 렌더링 비용 깎기 🟢

영수(BE): "그럼 리액트가 화면을 알아서 동기화(업데이트) 해준다고 했잖아. 상태가 바뀔 때마다 화면 전체를 새로고침해서 처음부터 다 다시 그리는 거야? 컴퓨터 터지겠다."

아니, 리액트에는 아주 영리한 중간 상인이 있어. 그걸 가상 돔(Virtual DOM) 이라고 불러.

🧒 5살에게 설명한다면?
도화지(실제 브라우저 화면)에 그려진 그림 전체를 지웠다 다시 그리면 너무 힘들어.
그래서 리액트는 '연습장(가상 돔)'에 먼저 진짜 빨리 상상으로 그림을 그려봐.
그리고 어제 그린 연습장이랑 오늘 그린 연습장을 딱 비교해.
"어? 나뭇잎 색깔만 빨간색으로 달라졌네?"
그러면 진짜 도화지 전체 리셋 안 하고, 바뀐 나뭇잎 부분에만 색칠을 한 번 덧칠(Patch) 하는 거야.

🔍 멘탈 모델로 그리기:

  1. 데이터(State)가 바뀐다.
  2. React가 새로운 UI를 가상 메모리(Virtual DOM) 에 전체 다 새로 그린다. (이 작업은 엄청나게 빠르다)
  3. 0.01초 전의 가상 돔과 방금 그린 가상 돔을 비교(Diffing)한다.
  4. 달라진 1%의 부분만 찾아내어 진짜 브라우저 DOM에 찔러 넣는다(Commit).

🧩 4. JSX의 진짜 모습 (착시 현상) 🟢

영철(신입): "오, 신기하네요. 그럼 이 코드는 뭔가요? 자바스크립트 파일(page.tsx) 안에 뜬금없이 HTML 태그가 들어있는데 에러가 안 나요!"

// 우리가 작성하는 JSX 코드
export function SearchFilter() {
  return (
    <div className="filter-box">
      <h1>검색 필터</h1>
    </div>
  );
}

사실, 저건 HTML이 아니야. 자바스크립트 엔진은 저 꺾쇠 < > 태그를 읽을 수 없어.
저 코드는 Babel이나 ESBuild 같은 컴파일러에 의해 브라우저에 도달하기 전 순수한 자바스크립트 함수(객체 리터럴) 로 은밀하게 번역돼.

// ✅ 브라우저가 실제로 읽는 자바스크립트 (JSX-Runtime 변환 후)
import { jsx as _jsx } from "react/jsx-runtime";
 
export function SearchFilter() {
  return _jsx("div", {
    className: "filter-box",
    children: _jsx("h1", {
      children: "검색 필터"
    })
  });
}

🔗 연결 고리
방금 위에서 "리액트는 연습장(가상 돔) 에 렌더링을 한다" 고 했지? 그 연습장이 바로 이 _jsx 함수가 만들어내는 무수한 자바스크립트 객체(Object) 덩어리들이야.
이 객체 트리를 비교해서 브라우저에 그리는 거지!


💥 에러 해결 카탈로그

실제로 프로젝트를 세팅하고 컴포넌트를 만들다 보면 아래와 같은 에러를 마주칠 거야.

Adjacent JSX elements must be wrapped in an enclosing tag

언제 나오는가?

function CommunityHeader() {
  return (
    <h1>영수네 커뮤니티</h1>
    <p>개발자들을 환영합니다</p> // 💥 에러 쾅!
  );
}

원인: JSX는 결국 JavaScript 함수(_jsx())의 반환값(Return)이라고 배웠지? 자바스크립트 함수는 무조건 단 하나의 값 만 반환할 수 있어. 값 두 개를 콤마 없이 띡 던질 수 없잖아.

해결책: 모든 걸 감싸는 빈 태그(Fragment)를 씌워주면 돼!

function CommunityHeader() {
  return (
    <>
      <h1>영수네 커뮤니티</h1>
      <p>개발자들을 환영합니다</p>
    </>
  );
}

🏁 이번에 배운 내용 총정리

오늘 배운 핵심을 한눈에 정리해볼까? 영수네 커뮤니티를 세팅하기 위한 완벽한 기초 공사였어.

📋 핵심 패턴

개념내가 이해한 의미5년 차의 설명
Vite빠른 스타트 툴구형 번들러(Webpack)를 버리고 ES Module을 활용해 HMR(빠른 갱신)을 극대화한 프론트엔드 빌드 툴.
Virtual DOM리액트의 도화지브라우저 DOM 조작 비용을 최소화하기 위해 메모리 상에서 미리 UI 변화를 계산(Diffing)하는 스냅샷 엔진.
JSXJS 안의 HTML사실 HTML이 아니라, 복잡한 React.createElement 객체 생성 함수를 사람이 읽기 쉽게 숨겨둔 문법적 설탕(Syntactic Sugar).

✅ 실습 후 체크리스트

  • 왜 요즘 프로젝트 세팅 시 CRA 명령어를 보면 도망쳐야 하는지 설명할 수 있다.
  • 리액트가 변경된 상태를 브라우저에 효율적으로 그리기 위해 쓰는 두 단계(가상 돔 비교 -> 커밋) 를 안다.
  • 컴포넌트 리턴값에 빈 태그 <></> 를 씌워야 하는 이유를 'JS 함수의 리턴 본질' 로 설명할 수 있다.

🐣 영철이의 퇴근 일기

휴, 오늘 영호 리드 님한테 제대로 팩폭 맞았다. 맨날 그냥 구글링해서 create-react-app 복붙하고 시작했는데, 그게 똥차를 몰고 고속도로 나가는 격이라니... 그래도 가상 돔이랑 JSX 원리를 이렇게 명쾌하게 듣고 나니까 확실히 프레임워크가 마법이 아니라 엄청나게 똑똑한 최적화의 결과라는 게 와닿는다.

💡 "CRA는 버리고 Vite로, 가상 돔은 똑똑한 연습장이자 스냅샷! 자바스크립트 함수의 리턴 본질을 이해하고 명령형 DOM 조작의 악몽에서 벗어나자."

이제부터 개인 프로젝트 세팅할 때는 무조건 Vite로 빠르게 치고 나가야지. 오늘따라 퇴근길 발걸음이 가벼운 걸 보니 코딩 실력이 1g 정도 늘었나 보다. 집에 가서 치맥 하면서 맥북 켜고 Vite 세팅 한 번 싹 밀고 새로 짜봐야겠다! 내일은 칭찬받길.


📝 마무리 퀴즈

Q1. 영철이가 다음 프로젝트를 세팅하려 합니다. 영호가 권장할 가장 바람직한 명령어는 무엇일까요?

  • A) npx create-react-app 커뮤니티앱
  • B) npm install react react-dom 후 직접 Webpack 세팅
  • 가) npm create vite@latest 커뮤니티앱 -- --template react-ts
  • 라) 바닐라 JS로 index.html<script> 태그를 추가하여 시작

정답:

💡 상세 해설: CRA(Create React App)는 내부 번들러로 Webpack을 사용하여 앱 전체를 매번 다시 빌드하기 때문에 파일 갯수가 늘어날수록 속도가 끔찍하게 느려집니다. 반면 Vite는 최신 브라우저의 ES Modules 지원을 활용하여 수정된 파일 단 하나만 즉시 교체(HMR)해 주므로, 개발 효율성 면에서 프론트엔드 생태계의 표준이 되었습니다!

Q2. "리액트는 Virtual DOM을 써서 무조건 빠르다"는 영철이의 말에 영호가 덧붙여야 할 설명으로 가장 적절한 것은?

  • A) "맞아, 가상 돔은 브라우저의 실제 돔(Real DOM)을 조작하는 것보다 메모리 소모가 적어서 항상 무조건 빨라."
  • B) "아니, 가상 돔을 그리는 자체비용(메모리 소모) 이 있기 때문에 아주 간단한 UI를 그릴 땐 바닐라 JS가 더 빠를 수 있어. 다만, 복잡한 상태 변화 시 여러 UI 변경 사항을 묶어서 단 한 번만 DOM에 칠하는(Batching/Patching) 효율성 덕분에 "유지보수와 성능의 훌륭한 타협점" 을 제공하는 거야."
  • 가) "가상 돔은 보안(XSS)을 막기 위해 존재하는 거라 속도랑은 관계가 없어."
  • 라) "가상 돔은 옛날 기술이라 지금은 쓰지 않아."

정답: B

💡 상세 해설: 많은 신입들이 착각하는 포인트입니다! 'React가 무조건 가장 빠르다' 는 틀린 멘탈 모델입니다. 진짜 DOM 조작을 수동으로 완벽하게 최적화해서 짠 바닐라 JS 코드보다 무조건 빠를 수는 없습니다. 하지만 애플리케이션 규모가 커지면 인간이 DOM 조작 로직을 완벽히 짜는 건 불가능에 가깝습니다. 리액트의 가상 돔은 "우리(개발자) 가 직접 DOM을 건드리는 수고를 덜어주면서도, 변경 사항을 모아 단 한 번 찔러줌으로써 충분히 납득할 만한 고성능" 을 보장하는 우아한 생태계입니다.

Q3. 다음 코드를 실행하면 컴파일 에러가 발생합니다. 바르게 고친 뒤, 왜 고쳐야만 하는지(근본 원인) 설명해보세요.

function LikeButton() {
  return (
    <button>좋아요</button>
    <span>10명</span>
  );
}

정답 및 주관식 해설:

고친 코드:

function LikeButton() {
  return (
    <>
      <button>좋아요</button>
      <span>10명</span>
    </>
  );
}

근본 원인(Why): 우리가 작성하는 저 마크업 태그는 사실 HTML이 아니라 자바스크립트의 _jsx() 라는 객체 생성 함수 호출문입니다. 자바스크립트에서 어떤 함수가 반환(return) 을 할 때는 무조건 딱 1개의 값만 던질 수 있습니다. 객체 2개를 콤마도 없이 던지면 Syntax Error가 나죠. 따라서 Fragment(<></>) 나 <div> 라는 하나의 큰 상자에 담아 그 상자 1개를 반환해야 정상적으로 자바스크립트 코드로 해석될 수 있습니다.


수고했어!
기초 세팅의 진실을 알았으니, 다음 레벨에서는 영철이가 가장 많이 터트리는 "02. 상태(State)의 진짜 의미 — 스냅샷의 비밀" 에 대해 깊이 파고들어 보자.