React를 배우기 전 알아둬야 할 기본 개념들을 정리했다. React가 뭔지, 왜 쓰는지부터 선언적 UI, Virtual DOM 개념, 그리고 순수 자바스크립트로 카운터를 직접 만들어보며 React 없이 화면을 다루는 게 어떤 느낌인지까지 다룬다.
React는 메타(구 페이스북)가 만든 자바스크립트 라이브러리로, 화면(UI)을 작은 조각인 컴포넌트 단위로 나눠서 만들고 조립할 수 있게 해준다.
핵심은 이거다: "화면에 무엇을 보여줄지"만 코드로 선언하면, 실제로 화면을 어떻게 바꿀지는 React가 알아서 처리해준다.
프론트엔드 채용 공고를 보면 "React 경험 우대" 또는 "React 필수"라는 문구가 거의 빠지지 않는다. 그만큼 현재 웹 프론트엔드 시장의 사실상 표준이다.
React는 2013년 페이스북이 오픈소스로 공개한 라이브러리로, 처음엔 페이스북 내부의 복잡한 뉴스피드 화면을 효율적으로 관리하기 위해 만들어졌다. 이후 전 세계 개발자들이 채택하면서 지금은 프론트엔드 개발의 사실상 표준 도구가 되었다.
React가 등장하기 전에는 화면을 바꾸려면 개발자가 직접 "어떤 요소를 찾아서, 어떻게 바꿀지"를 하나하나 코드로 지정해야 했다.
// 순수 JavaScript로 화면 업데이트하기
const countEl = document.getElementById("count");
let count = 0;
count = count + 1;
countEl.innerText = count;
화면 요소가 몇 개 안 될 때는 문제없지만, 다뤄야 할 데이터와 화면 요소가 많아질수록 "지금 화면에 뭐가 보이고 있는지"를 개발자가 머릿속으로 계속 추적해야 해서 코드가 점점 복잡해진다.
왜 힘들어질까? 버튼 하나, 텍스트 하나를 바꾸는 정도는 괜찮지만, 화면 곳곳의 값들이 서로 연결된 서비스(예: 장바구니 개수가 바뀌면 총 금액도, 배지 숫자도 같이 바뀌어야 하는 경우)에서는 "어디를 빼먹지 않고 다 업데이트했는가"를 사람이 일일이 확인해야 한다. 이 과정에서 실수가 생기기 쉽고, 코드도 점점 읽기 어려워진다.
React를 이해하는 가장 중요한 개념이 바로 선언적(Declarative) UI다.
| 구분 | 설명 | 비유 |
|---|---|---|
| 명령형 (Imperative) | "어떻게" 화면을 바꿀지 순서대로 지시 | 요리사에게 "물을 끓이고, 면을 넣고, 3분 기다리고..." 단계를 일일이 지시 |
| 선언적 (Declarative) | "무엇을" 보여줄지 결과만 선언 | 요리사에게 "라면 하나 주세요"라고 결과만 요청 |
React에서는 count라는 값이 바뀌면 화면에 "count 값을 보여준다"고 선언만 해두면, 값이 바뀔 때 화면을 어떻게 바꿀지는 React가 알아서 계산해서 처리해준다.
왜 선언적 방식이 유지보수에 유리할까? 명령형 방식은 화면이 복잡해질수록 "이 코드를 실행하면 화면이 어떤 상태가 될까"를 예측하기 어려워진다. 선언적 방식은 "지금 상태(state)가 이거니까 화면은 이렇게 보여야 한다"만 정의하면 되기 때문에, 코드를 읽는 사람이 결과를 예측하기 쉽고 버그를 찾기도 수월하다.
실제 브라우저 화면(DOM)을 매번 직접 바꾸는 작업은 컴퓨터 입장에서 비용이 큰 작업이다. React는 이 문제를 해결하기 위해 Virtual DOM이라는 가상의 화면 복사본을 메모리 위에 만들어둔다.
왜 필요할까? 화면 전체를 매번 새로 그리면 느려질 수밖에 없다. Virtual DOM 덕분에 React는 바뀐 부분만 정확히 찾아서 최소한으로만 실제 화면을 업데이트하기 때문에, 복잡한 화면에서도 좋은 성능을 유지할 수 있다.
💡 참고: 최신 React는 내부적으로 더 정교한 렌더링 최적화를 사용하지만, 지금 단계에서는 "달라진 부분만 효율적으로 업데이트한다"는 개념만 이해하면 충분하다.
| 구분 | React | Vue | Angular |
|---|---|---|---|
| 성격 | UI 라이브러리 | 프레임워크 | 풀스택 프레임워크 |
| 학습 난도 | 중간 | 쉬움 | 어려움 |
| 문법 | JSX (HTML+JS 결합) | 템플릿 + JS 분리 | TypeScript 기반 |
| 국내외 채용 시장 | 가장 넓음 | 국내 일부 | 상대적으로 적음 |
왜 React부터 배울까? 세 도구 모두 "선언적으로 UI를 만든다"는 목표는 같지만, React는 채용 시장 규모와 생태계, 참고 자료의 양에서 압도적으로 유리하다. React의 개념(컴포넌트, state, props)을 익혀두면 Vue나 Angular도 상대적으로 쉽게 배울 수 있다.
React 없이 순수 JavaScript만으로 "버튼을 클릭하면 숫자가 올라가는" 카운터를 직접 만들어보면서, DOM을 직접 조작하는 방식이 실제로 어떤 느낌인지 체험해본다.
진행 순서
index.html 파일을 생성한다.<!DOCTYPE html>
<html>
<body>
<h1 id="count">0</h1>
<button id="increase">+1</button>
<script>
let count = 0;
const countEl = document.getElementById("count");
const buttonEl = document.getElementById("increase");
buttonEl.addEventListener("click", function () {
count = count + 1;
countEl.innerText = count;
});
</script>
</body>
</html>
index.html 파일을 열어 버튼을 눌러보고, 숫자가 올라가는지 확인한다.이 코드의 한계는 무엇일까? 지금은 숫자 하나만 다루니 간단해 보이지만, 만약 카운터가 5개, 10개로 늘어난다면 어떻게 될까? getElementById로 요소를 찾는 코드와 innerText를 갱신하는 코드를 카운터 개수만큼 반복해서 작성해야 한다. React를 배우고 나면, 같은 기능을 훨씬 적은 코드로, 개수가 늘어나도 복잡해지지 않는 방식으로 만들 수 있다.
F12 또는 우클릭 → 검사를 눌러 개발자 도구를 연다.<h1 id="count"> 태그의 내용이 실제로 바뀌는 것을 실시간으로 확인한다.count 변수 값을 직접 출력해보며, 화면에 보이는 값과 실제 변수 값이 어떻게 연결되어 있는지 확인한다.React를 사용하면 이런 DOM 업데이트 과정을 개발자가 직접 신경 쓰지 않아도 된다.
React 프로젝트를 시작하기 전 알아야 할 개발 환경을 정리했다. Node.js와 npm이 뭔지부터, Vite로 프로젝트를 만드는 이유, 폴더 구조, 핫 리로드, React 개발자 도구까지 다룬다.
Node.js는 원래 브라우저 안에서만 실행되던 자바스크립트를 브라우저 밖(컴퓨터)에서도 실행할 수 있게 해주는 실행 환경이다. React 프로젝트를 만들고 실행하려면 이 Node.js가 반드시 설치되어 있어야 한다.
Node.js를 설치하면 npm(Node Package Manager)이라는 도구가 함께 설치된다. npm은 다른 개발자들이 만들어 둔 라이브러리(패키지)를 검색하고 설치·관리해주는 도구다.
node -v # 설치된 Node.js 버전 확인
npm -v # 설치된 npm 버전 확인
React 프로젝트를 새로 시작할 때는 파일을 하나하나 손으로 만들지 않고, Vite라는 도구로 기본 뼈대를 자동으로 생성한다.
npm create vite@latest
| 구분 | 설명 |
|---|---|
| Vite | 빠른 개발 서버와 빌드 속도를 제공하는 최신 프로젝트 생성 도구 |
| 개발 서버 | 코드를 저장하는 즉시 브라우저 화면에 결과를 반영해주는 서버 |
| 빌드 | 완성된 코드를 실제 배포 가능한 파일로 압축·최적화하는 과정 |
Vite는 왜 예전 도구(Create React App)보다 빠를까? 예전에는 프로젝트를 실행할 때마다 코드 전체를 한 번에 변환(번들링)했기 때문에 파일이 많아질수록 시작 속도가 느려졌다. Vite는 필요한 파일만 그때그때 변환해서 브라우저에 전달하는 방식을 쓰기 때문에, 프로젝트 규모가 커져도 개발 서버가 빠르게 켜지고 반응한다.
Vite로 프로젝트를 생성하면 아래와 같은 폴더 구조가 자동으로 만들어진다.
| 파일/폴더 | 역할 |
|---|---|
index.html | 브라우저가 최초로 불러오는 진입 파일 |
src/main.jsx | React 앱을 화면에 처음 그려주는 시작점 코드 |
src/App.jsx | 우리가 실제로 화면을 만들어갈 최상위 컴포넌트 |
public/ | 이미지 등 그대로 제공되어야 하는 정적 파일 보관 폴더 |
package.json | 프로젝트 이름, 설치된 라이브러리 목록 등을 관리하는 설정 파일 |
node_modules/ | npm으로 설치한 라이브러리들이 실제로 저장되는 폴더 |
npm run dev
위 명령어를 실행하면 개발 서버가 켜지고, 터미널에 나오는 주소(예: http://localhost:5173)로 접속하면 화면을 확인할 수 있다.
코드를 수정하고 저장하면, 브라우저를 새로고침하지 않아도 화면이 자동으로 바뀌는데 이 기능을 핫 리로드(Hot Reload)라고 부른다.
핫 리로드는 왜 개발 속도를 크게 높여줄까? 핫 리로드가 없다면 코드를 한 줄 고칠 때마다 브라우저를 직접 새로고침하고, 어떤 동작을 했었는지(예: 로그인 후 특정 페이지 이동) 다시 반복해야 한다. 핫 리로드는 지금 화면 상태를 유지한 채로 바뀐 코드만 반영해주기 때문에, 작은 수정을 훨씬 빠르게 확인하며 개발할 수 있다.
크롬 확장 프로그램인 React Developer Tools를 설치하면, 개발자 도구(F12)에 Components, Profiler 탭이 추가된다.
이 탭에서 현재 화면에 어떤 컴포넌트들이 어떤 props와 state 값을 가지고 있는지 실시간으로 확인할 수 있다.
src/main.jsx(시작점) → src/App.jsx(최상위 컴포넌트) 흐름을 기억해두면 프로젝트 구조가 헷갈리지 않는다.React의 핵심 문법인 JSX를 정리했다. JSX가 뭔지부터 표현식 사용법, HTML과 다른 속성 규칙, Fragment, 조건부 스타일링까지 다룬다.
JSX는 자바스크립트 코드 안에 HTML과 비슷한 태그를 직접 작성할 수 있게 해주는 문법이다. 겉모습은 HTML과 비슷하지만, 실제로는 Vite 같은 빌드 도구가 브라우저에 전달하기 전에 자바스크립트 함수 호출로 변환(컴파일)해주는 자바스크립트 문법이다.
아래 두 코드는 둘 다 React 코드이고, 화면에 완전히 같은 결과를 보여준다. 차이는 "화면 구조를 어떤 문법으로 표현하는가"뿐이다.
JSX 없이 작성한 경우
import React from "react";
function App() {
const name = "한코딩";
return React.createElement(
"div",
null,
React.createElement("h1", null, name + "님 안녕하세요"),
React.createElement("p", null, "오늘도 좋은 하루 되세요!")
);
}
export default App;
JSX로 작성한 경우
function App() {
const name = "한코딩";
return (
<div>
<h1>{name}님 안녕하세요</h1>
<p>오늘도 좋은 하루 되세요!</p>
</div>
);
}
export default App;
React.createElement는 태그가 늘어날수록 괄호와 쉼표가 계속 겹쳐져서 구조를 한눈에 파악하기 어려워지지만, JSX는 실제 화면 구조와 거의 똑같이 생겨서 훨씬 읽고 쓰기 쉽다. 또한 {name}처럼 중괄호로 변수를 바로 끼워 넣을 수 있다는 점은 순수 HTML로는 절대 할 수 없는, JSX만의 문법이다.
JSX 안에서 중괄호 { }를 쓰면, 그 안에 자바스크립트 값이나 계산 결과를 그대로 화면에 출력할 수 있다.
function App() {
const name = "한코딩";
const age = 25;
return (
<div>
<p>이름: {name}</p>
<p>나이: {age + 1}세 (내년 기준)</p>
</div>
);
}
export default App;
왜 { } 안에는 if문이나 for문을 바로 쓸 수 없을까? { } 안에는 "값으로 결과가 나오는 것"(표현식)만 들어갈 수 있다. if문이나 for문은 값이 아니라 "명령"이기 때문에 그 자리에 바로 쓸 수 없다. 조건에 따라 다른 값을 보여주고 싶을 때는 삼항 연산자(조건 ? A : B)처럼 값을 결과로 돌려주는 표현식을 사용해야 한다.
JSX는 HTML과 비슷하지만 몇 가지 다른 규칙이 있다.
| HTML | JSX | 이유 |
|---|---|---|
class="box" | className="box" | class는 자바스크립트에서 이미 다른 용도(클래스 문법)로 쓰이는 예약어라서 |
<label for="name"> | <label htmlFor="name"> | for도 자바스크립트의 반복문 예약어와 겹치기 때문에 |
onclick="..." | onClick={...} | 이벤트 속성은 카멜케이스(camelCase)로 작성 |
style="color:red" | style={{ color: "red" }} | style에는 문자열이 아니라 자바스크립트 객체를 전달 |
아래 코드는 문법 오류가 발생하는 예시다.
// ❌ 오류가 나는 코드
function App() {
const handleClick = () => {
alert("버튼을 눌렀습니다!");
};
return (
<div class="box" style={ color: "blue", fontSize: "20px" }>
<p>이 글씨는 파란색, 20px 크기입니다.</p>
<button onClick={handleClick}>클릭</button>
</div>
);
}
export default App;
올바르게 고치면 이렇게 된다.
// ✅ 올바른 코드
function App() {
const handleClick = () => {
alert("버튼을 눌렀습니다!");
};
return (
<div className="box" style={{ color: "blue", fontSize: "20px" }}>
<p>이 글씨는 파란색, 20px 크기입니다.</p>
<button onClick={handleClick}>클릭</button>
</div>
);
}
export default App;
style에는 왜 문자열이 아니라 객체를 넣어야 할까? HTML에서는 style="color:red; font-size:14px"처럼 문자열로 스타일을 적지만, JSX에서는 { color: "red", fontSize: "14px" }처럼 자바스크립트 객체로 적는다. 속성 이름도 font-size가 아니라 fontSize처럼 카멜케이스로 바뀐다. 이렇게 하면 변수 값을 스타일에 바로 연결하기가 훨씬 쉬워진다.
⚠️
style={{ ... }}처럼 중괄호가 두 개인 이유: 바깥{}는 "JS 표현식을 쓰겠다"는 JSX 문법이고, 안쪽{}는 그 표현식이 "자바스크립트 객체"라는 뜻이다.
JSX에서 return은 반드시 하나의 태그만 반환할 수 있다. 아래 코드는 에러가 발생한다.
// ❌ 오류가 나는 코드
function App() {
return (
<h1>제목</h1>
<p>내용</p>
);
}
export default App;
이 문제는 보통 <div>로 감싸서 해결하지만, 화면에는 필요 없는 <div>가 계속 추가되는 문제가 생긴다. 이럴 때 Fragment(<>...</>)를 사용하면 실제 화면에 태그를 남기지 않으면서 여러 요소를 묶을 수 있다.
// ✅ Fragment로 감싸기
function App() {
return (
<>
<h1>제목</h1>
<p>내용</p>
</>
);
}
export default App;
Fragment는 왜 필요할까? <div>로 계속 감싸다 보면 실제로는 필요 없는 <div> 태그가 화면 곳곳에 쌓여서, 나중에 CSS 레이아웃을 짤 때 예상치 못한 간격이나 스타일 문제가 생길 수 있다. Fragment는 "화면에는 안 남기고 문법적으로만 묶어주는" 투명한 포장지 역할을 한다.
삼항 연산자를 활용하면 조건에 따라 다른 클래스나 스타일을 적용할 수 있다.
function App() {
const isActive = true;
return (
<span className={isActive ? "badge-on" : "badge-off"}>
{isActive ? "활성" : "비활성"}
</span>
);
}
export default App;
className이 실제로 바뀌는지는 크롬 개발자 도구의 Elements 탭에서 확인할 수 있다. 아직 CSS를 연결하지 않았다면 글자만 바뀌고 색상 변화는 없는 게 정상이다.
src/App.jsx를 열고 기존 내용을 모두 지운다.{ }를 사용해 변수들을 출력하는 <p> 태그들을 작성한다 (export default App; 빠뜨리지 않기).hobbies.length 등)처럼 계산 결과도 함께 출력해본다.App 함수 안에 isOnline이라는 boolean 변수를 하나 만든다.isOnline 값에 따라 className이 "online" 또는 "offline"으로 바뀌도록 만든다.true일 때와 false일 때 화면에 다른 텍스트("온라인" / "오프라인")도 함께 표시해본다.style={{ }} 문법으로 원하는 텍스트에 색상과 글자 크기를 직접 지정해본다.isOnline 값에 따라 색상이 초록색/회색으로 바뀌도록 만들어, 조건부 스타일까지 함께 연습해본다.React.createElement() 호출을 더 읽기 쉽게 써주는 문법일 뿐이다.{ } 안에는 값(표현식)만 들어갈 수 있고, if/for 같은 명령문은 들어갈 수 없다는 걸 기억하자. 조건 분기는 삼항 연산자로 처리한다.class → className, for → htmlFor, style="..."→ style={{...}} 같은 HTML과의 차이는 자바스크립트 예약어 충돌을 피하기 위한 규칙이다.<div> 대신 Fragment(<>...</>)를 쓰는 습관을 들이자.