사용 이유
- 새 페이지 들어가면 부드럽게 전환
- 컴포넌트로 코드 재사용성 늘리기
- html로 코딩하면 어떻게 바꿔야하는지 일일이 지시해야하고, 수정시에 바꿔야하는 곳이 많다
-> 유지보수가 어려움
- JS와 HTML을 한큐에 처리 가능
-> 무엇을 보여줄지만 정의할 수 있음
- 데이터가 변경되면 화면은 자동으로 업데이트
React 플젝 시작
Vite로 만든다. 환경설정을 자동으로 빠르게 세팅해준다.


현재 프로젝트에서 바로 만들었으면 좋겠으면 npm create vite@latest .
(온점넣어줘야함)을 해줘야한다.

이렇게 세팅하고, 프로젝트로 이동한 후
npm i로 필요한 패키지를 설치한다.
그러고 npm run dev해서 개발 서버 실행하면 된다.
실행하면 다음과 같은 기본 화면이 나온다

이 세팅을 boilerplate라고 부른다.
프로젝트 시작을 위한, 준비 셋업 템플릿이다.
Vite가 좋은 이유 - HMR (Hot Module Replacement)
변경한 파일만 새로 다운로드 받는 방식 -> 그래서 훨씬 더 빠름

JSX
- Javascript안에 HTML이 들어가 있는 문법 (Javascript XML)
- 내부적으로 데이터가 어떻게 변환되는지 몰라도, 데이터 변경 시 알아서 화면이 전환됨

<규칙>
1. 반드시 하나의 태그로 감싸고 리턴해야함
적어도 Fragment로 감싸줘야한다.
(모두 div로 감싸주면 HTML 구조가 너무 복잡해진다.)
function TableRow() {
return (
<>
<td>이름</td>
<td>나이</td>
</>
);
}
//<React.Fragment>도 있긴 한데 길어서 잘 안 사용한다.
React.Fragment는 map을 사용해서 key를 빈태그에 작성해줘야할때 필요하다.
2. HTML 속성은 camelCase로 작성해야함

3. 모든 태그는 닫아야 함
JSX에서 JS 사용하기
- JSX안에 JS 표현식을 넣고 싶을때 {}를 사용한다.
- {}안에 함수 실행해서 결과값을 넣을수도 있으니,
연산 방식이 복잡하면 함수로 빼는게 좋다.
⭐JSX는 뭘 보여줄지에 집중하고, 어떻게 계산할지는 함수로 빼라
- 삼항 연산자는 좋으나,
중첩 삼항 연산잔는 이해하기 어렵기 시작함으로 함수로 따로 분리하여
- 리스트를 화면 보여줄때 가장 자주 사용하는 패턴이 map과 filter이다.
⭐key에다가 index를 넣지 말아라, 나중에 CRUD할때 문제가 생긴다. -> 안티패턴
고유한 ID를 key에 넣어줘야한다.
- 템플릿 리터럴으로 어떤 상태에 어떤 class를 가져라를 표현한다.
- JSX내에 IIFE사용하는것도 읽기 어려워지므로 지양해야한다. -> 안티패턴

Component
화면을 구성하는 독립적인 부품
JSX를 반환하는 함수이다.

function Welcome() {
return <h1>환영합니다!</h1>;
}
- PascalCase로 시작해야함 (WritePostPage)


Props
정의
부모컴포넌트가 자식 컴포넌트에 보내는 property (데이터)
다만, 자식은 props를 절대 수정할 수 없다.

값을 가공하고 싶으면 새 변수에 할당해주는 것이 좋다.
// 자식 컴포넌트
function Greeting(props) {
return <h1>안녕하세요, {props.name}님!</h1>;
}
// 부모 컴포넌트
function App() {
return (
<div>
<Greeting name="홍길동" />
<Greeting name="김철수" />
<Greeting name="이영희" />
</div>
);
}
근데 이걸 구조분해하면 훨씬 편하게 사용할 수 있다.
- boolean 타입은 true인 경우에 생략이 가능하다.
function UserCard({ name, age, job }) { // ← 이 부분!
return (
<div>
<h2>{name}</h2>
<p>{age}세</p>
<p>{job}</p>
</div>
);
}
<Counter count={0} /> {/* 숫자 */}
<Modal isOpen={true} /> {/* 불린 */}
<Button disabled={true} /> // 정석
<Button disabled /> // 축약 (true와 동일!)
<TagList tags={['React', 'JS', 'CSS']} /> {/* 배열 */}
<Profile user={{ name: '홍길동', age: 25 }} /> {/* 객체 */}
<Form onSubmit={handleSubmit} /> {/* 함수 */}
Props의 기본값 정해주기
function Button({ text = '버튼', color = 'blue' }) {
return <button style={{ color }}>{text}</button>;
}
// 사용
<Button /> {/* "버튼", 파란색 */}
<Button text="클릭" /> {/* "클릭", 파란색 */}
<Button text="클릭" color="red" /> {/* "클릭", 빨간색 */}
Spread 연산자로 한번에 전달하기
객체를 통째로 넘기고 싶을때 ...을 쓰면 편하다.
function App() {
const user = {
name: '홍길동',
age: 25,
job: '개발자'
};
// 😐 일일이 전달
return <UserCard name={user.name} age={user.age} job={user.job} />;
// ✨ Spread 연산자 (훨씬 간결!)
return <UserCard {...user} />;
}

Children
부모의 컨포넌트 태그 사이에 들어가는 JSX 내용이다.
밑의 예시에서 <h2>와 <p>가 children이다.
<Card>
<h2>제목</h2>
<p>설명</p>
</Card>

주의해야하는 안티패턴



⭐children이 빈 경우를 꼭 챙겨야한다.
State
컴포넌트가 기억하는 값이다. 얘가 변하면 화면이 자동 업데이트한다.
React Memory 내에 정의되기 때문에 리렌더링 되어도 값을 유지한다. (메모리가 함수 바깥에 위치되어 있음)
배경
⭐상태가 변경되면, 리렌더링이 일어난다 (해당 컴포넌트 전체가 다시 불려와짐)
이때, 변수들은 리렌더링 시 모두 새 변수(이전과 다른 주소를 가진)를 정의해서 사용하기 때문에,
일반 변수들을 수정하는 식으로 코드를 짜면 딱 한번만 변하고 그 이후는 변하지 않는다

onClick을 누르면 count state는 의도대로 잘 바뀐다.
반면 변수로 정의한 count2는 11로 바뀌고, 리렌더링 되어서 완전히 새 count2가 정의되어
앞으로 눌러도 영원히 11이 된다. .
문법

두가지 state 업데이트 방식

function handleClick() {
// 방법 1: 직접 값
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// → 1만 증가 😱
// 방법 2: 함수 전달
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// → 3 증가 ✅
}
이렇게 작동하는 이유가, React가 배치처리(Batching)을 하기 때문이다.


화살표 함수는 각자 다른 참조값을 가지기 때문에
서로 다른 값을 참조한다.
그래서 모아서 한번에 처리해도 이전 상태를 잘 받아온다.
참조형 state
객체와 배열을 state로 쓸때의 함정을 피하고, 불변성을 지키며 안전하게 업데이트해야한다.
React는 state가 변했는지 확인할때 ===(참조) 비교를 한다.
그래서 참조주소가 바뀌어지만 화면 리렌더링을 한다.
⭐리액트는 상태변경 요청을 받았을 때 현재 상태와 다음 상태가 다를때만 리렌더링을 실시한다.
const obj = { name: '홍길동' };
obj.name = '김철수'; // 값만 바꿈
setUser(obj); // 같은 주소의 객체
// React: "어? 주소가 똑같네? 안 바뀐 거네!" 😴
// → 화면 업데이트 안 함!
그래서 새로운 객체/배열을 setState안에 새로 할당해줘야한다.
수정액으로 지우지 말고, 복사본을 만들어서 새로 써야한다
(불변성 - Immutability, 원본을 건들지 말고 카피해서 새로 만들어라)
// ✅ 새 객체 생성 (React가 알아챔!)
setUser({ ...user, name: '김철수' });
⭐ 기존것 다 펼치고, 바꿀 것만 덮어쓰기하면 된다.
//추가하는 방식: spread operator사용
function addTodo(newTodo) {
setTodos([...todos, newTodo]); // 새 배열! ✨
}
//삭제하는 방식: filter은 새 배열 만들기에 작동함
function deleteTodo(id) {
setTodos(todos.filter(todo => todo.id !== id));
// ↑↑↑↑↑↑
// "id가 일치하지 않는 것만 남겨줘"
}
//수정하는 방식: map도 새 배열 만들기에 작동함
function toggleTodo(id) {
setTodos(todos.map(todo =>
todo.id === id
? { ...todo, done: !todo.done } // 해당 항목만 바꾸기
: todo // 나머진 그대로
));
}
(sort, revert, pop, etc는 모두 원본을 바꾸기에 사용하지 않는다)

주의점




React가 돌아가는 방식
React가 렌더링하는 방식
학습 목표: React의 엔진룸을 들여다본다. 어떻게 그렇게 빠르게 화면을 업데이트하는지, 그 마법의 정체를 밝힌다.
(1) 리액트에서 말하는 "렌더링"이 정확히 뭐예요?
놀랍게도 렌더링 = 컴포넌트 함수를 실행하는 것 이에요.
function Counter() {
const [count, setCount] = useState(0);
return <button>{count}</button>;
}
렌더링된다 = React가 Counter() 를 호출한다는 뜻.
🤯 충격의 진실
렌더링 ≠ 화면이 실제로 바뀌는 것!
"렌더링"
↓
함수 실행 → JSX 반환 → 가상 DOM 만들기
↓
"그 다음에" 화면 업데이트 (별개의 단계!)
많은 사람이 헷갈리는 부분이에요. 기억하세요:
렌더링은 "계산"이고, 화면 업데이트는 "반영"이에요.
(2) 두 단계로 나뉘어요
React의 렌더링은 2단계로 이루어져요.
1️⃣ Render Phase (계산 단계) — "설계도 그리기"
- 컴포넌트 함수 호출
- JSX 반환
- 가상 DOM 생성
- 이전 가상 DOM과 비교
2️⃣ Commit Phase (적용 단계) — "실제 건설"
- 실제 DOM에 변경사항 반영
- 화면에 보여지는 순간
🏗️ 비유: 건축 공사와 똑같아요.
- Render = 건축가가 설계도 그리기 (머릿속으로만)
- Commit = 인부들이 실제로 건물 짓기 (눈에 보이는 결과)
설계도를 여러 번 고쳐도 실제 공사는 최종 버전 한 번만 하죠? React도 똑같아요!
(3) 언제 렌더링될까?
컴포넌트가 다시 렌더링되는 4가지 경우예요.
1. 처음 화면에 나타날 때 (최초 렌더링)
<App /> // 앱이 시작되면 한 번 렌더링
2. State가 바뀔 때
setCount(count + 1); // → 리렌더링!
3. Props가 바뀔 때
<UserCard name={newName} /> // name이 바뀌면 → 리렌더링!
4. 부모가 렌더링될 때 ⚠️
function Parent() {
return (
<div>
<Child /> {/* 부모가 렌더링되면 자식도 같이! */}
</div>
);
}
🚨 중요: 부모가 렌더링되면 자식은 Props가 안 바뀌어도 리렌더링돼요!
이게 성능 문제의 주요 원인이기도 해요. (나중에 최적화로 해결!)
(4) Virtual DOM — 똑똑한 비서
🤔 왜 Virtual DOM이 필요해?
DOM 직접 조작은 무거워요. 한 번 건드릴 때마다 브라우저가 전체 화면을 다시 계산해야 해요.
// 10번 DOM 조작 = 10번 계산 😩
document.getElementById('item1').textContent = '...';
document.getElementById('item2').textContent = '...';
// ...
🧠 Virtual DOM의 아이디어
"머릿속으로 먼저 해보고, 최종 결과만 실제로 적용하자!"
바뀐 부분만 실제 DOM에 반영하는 것이다.
1. State 변경!
↓
2. 🧠 새 Virtual DOM 만들기 (메모리에서만)
↓
3. 🔍 이전 Virtual DOM과 비교 (Diffing)
↓
4. 📝 "아, 이것만 바뀌었네!" 최소 변경 목록 작성
↓
5. 🎨 실제 DOM에 그 부분만 반영 (Reconciliation)
🧑🍳 비유: 요리 시뮬레이션
❌ Virtual DOM 없이
냉장고 열기 → 확인 → 닫기
냉장고 열기 → 확인 → 닫기
냉장고 열기 → 확인 → 닫기
...
✅ Virtual DOM 방식
🧠 머릿속으로 전부 시뮬레이션
↓
📝 "필요한 재료 10개" 리스트 작성
↓
🥶 냉장고 한 번만 열어서 다 꺼내기!
훨씬 효율적이죠?
(5) 배치 업데이트 (Batching) — 영리한 모아 처리
여러 setter를 한 번에 처리하는 React의 최적화 기능이에요.
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
function handleClick() {
setCount(count + 1); // 렌더링 예약
setName('업데이트됨'); // 렌더링 예약
setCount(count + 2); // 렌더링 예약
// → 이벤트가 끝난 뒤 "한 번만" 렌더링!
}
console.log('렌더링!'); // 한 번만 출력됨
}
🤔 왜 이렇게 해?
만약 배치 안 하면? 3번 렌더링해서 화면이 번쩍번쩍 거려요. 😵
배치 처리로 마지막 결과만 한 번 렌더링 → 부드럽고 빠름! ✨
💡 React 18부터는 setTimeout, Promise 안에서도 자동 배치가 돼요. (예전엔 안 됐음!)
JSX로 자유롭게 클래스 추가하기
동적 클래스 조합
템플릿 리터럴 JS로 클래스를 조합할 수 있다 .
function Button({ variant }) {
return (
<button className={`btn btn-${variant}`}>
클릭
</button>
);
}
// 사용
<Button variant="primary" /> {/* className="btn btn-primary" */}
<Button variant="danger" /> {/* className="btn btn-danger" */}
조건부 클래스 3가지 패턴
1) 삼항 연산자 (단일 조건)
function TodoItem({ todo }) {
return (
<div className={`todo-item ${todo.done ? 'done' : ''}`}>
{todo.text}
</div>
);
}
2) && 연산자 (조건부 추가) ❌
function Button({ disabled }) {
return (
<button className={`btn ${disabled && 'btn-disabled'}`}>
클릭
</button>
);
}
- disabled가 true면 → "btn btn-disabled"
- false면 → "btn false" ⚠️ (어? false가 들어가네?!)
🚨 주의! false && 'xxx'는 false를 반환해요. 그럼 className에 "false" 라는 문자가 들어가요!
대신 이렇게 쓰세요: disabled ? 'btn-disabled' : '' (삼항) 또는 다음 패턴 사용!
3) 배열 + filter + join (여러 조건)
- 여러 class들을 배열로 넣는다.
-
function Button({ variant, size, disabled }) {
const classNames = [
'btn',
`btn-${variant}`,
`btn-${size}`,
disabled && 'btn-disabled'
].filter(Boolean).join(' ');
// ↑↑↑↑↑↑↑↑↑↑↑↑
// false/null/undefined 제거 후 공백으로 합치기
return <button className={classNames}>클릭</button>;
}
CSS Modules - 파일별 스코프 격리 (강력추천)
이게 없으면 다른 css 파일이라도, 안에 같은 이름의 클래스를 다루면
둘이 충돌이 나게 된다.
CSS Modules를 사용하면 css 파일이 자신만의 스코프를 가져서 문제가 생기지 않는다.
'코드잇 스터디' 카테고리의 다른 글
| [React] 리액트로 웹사이트 만들기 (0) | 2026.04.28 |
|---|---|
| [React] React로 데이터 다루기 (1) | 2026.04.22 |
| [JS 기초문법] 리퀘스트 보내기 (0) | 2026.04.14 |
| [JS 기초문법] 인터랙티브 JS (0) | 2026.04.09 |
| [JS기초문법] JS 복습해야할 헷갈리는 문법 (0) | 2026.04.06 |