Backend
home
⚔️

React 기초와 활용

생성일
2026/04/11 06:07
태그
React
React는 Facebook(현 Meta)이 만든 UI 라이브러리다. 컴포넌트 단위로 화면을 구성하고, 상태가 바뀌면 자동으로 화면을 다시 그려준다. 처음엔 개념이 낯설 수 있지만, 핵심 원리 몇 가지만 이해하면 빠르게 익숙해진다. 이 글에서는 React의 기초 개념부터 실전에서 자주 쓰는 패턴까지 정리한다.

React를 쓰는 이유

일반 JavaScript로 DOM을 직접 조작하면 코드가 복잡해지고 유지보수가 어려워진다. React는 상태(state)만 바꾸면 화면이 자동으로 업데이트되기 때문에 DOM을 직접 건드릴 필요가 없다. 컴포넌트 단위로 UI를 쪼개서 재사용할 수 있다는 것도 큰 장점이다.

프로젝트 세팅

Vite를 사용하면 빠르게 React 프로젝트를 시작할 수 있다.
npm create vite@latest my-app -- --template react cd my-app npm install npm run dev
Bash
복사
TypeScript를 함께 쓰고 싶다면 템플릿을 react-ts로 바꾸면 된다.

컴포넌트 기초

React에서 UI의 최소 단위는 컴포넌트다. 함수형 컴포넌트가 현재 표준이다.
const Hello = () => { return <h1>안녕, React!</h1>; }; export default Hello;
JavaScript
복사
JSX 문법을 사용하기 때문에 JavaScript 안에서 HTML처럼 작성할 수 있다. 단, class 대신 className을 쓰는 점에 주의한다.

Props로 데이터 전달하기

부모 컴포넌트에서 자식 컴포넌트로 데이터를 넘길 때 props를 사용한다.
// 자식 컴포넌트 const UserCard = ({ name, age }) => { return ( <div className="card"> <p>이름: {name}</p> <p>나이: {age}</p> </div> ); }; // 부모 컴포넌트 const App = () => { return ( <div> <UserCard name="김철수" age={25} /> <UserCard name="이영희" age={30} /> </div> ); }; export default App;
JavaScript
복사
props는 읽기 전용이다. 자식 컴포넌트에서 직접 수정할 수 없다.

useState로 상태 관리하기

컴포넌트 안에서 변하는 값을 다룰 때 useState를 사용한다. 상태가 바뀌면 컴포넌트가 자동으로 다시 렌더링된다.
import { useState } from 'react'; const Counter = () => { const [count, setCount] = useState(0); return ( <div> <p>현재 카운트: {count}</p> <button onClick={() => setCount(count + 1)}>+1</button> <button onClick={() => setCount(count - 1)}>-1</button> <button onClick={() => setCount(0)}>초기화</button> </div> ); }; export default Counter;
JavaScript
복사
useState(0)에서 0은 초기값이다. setCount를 통해서만 상태를 변경해야 한다. 직접 count = 1처럼 바꾸면 화면이 업데이트되지 않는다.

이벤트 처리

React에서 이벤트는 카멜케이스로 작성한다. onclick 대신 onClick, onchange 대신 onChange를 쓴다.
import { useState } from 'react'; const InputExample = () => { const [value, setValue] = useState(''); const handleChange = (e) => { setValue(e.target.value); }; const handleSubmit = () => { alert(`입력값: ${value}`); setValue(''); }; return ( <div> <input type="text" value={value} onChange={handleChange} placeholder="텍스트를 입력하세요" /> <button onClick={handleSubmit}>제출</button> <p>현재 입력: {value}</p> </div> ); }; export default InputExample;
JavaScript
복사

조건부 렌더링

상태에 따라 다른 UI를 보여줄 때 사용한다.
import { useState } from 'react'; const LoginStatus = () => { const [isLoggedIn, setIsLoggedIn] = useState(false); return ( <div> {isLoggedIn ? ( <p>환영합니다! 로그인 상태입니다.</p> ) : ( <p>로그인이 필요합니다.</p> )} <button onClick={() => setIsLoggedIn(!isLoggedIn)}> {isLoggedIn ? '로그아웃' : '로그인'} </button> </div> ); }; export default LoginStatus;
JavaScript
복사

리스트 렌더링

배열 데이터를 화면에 출력할 때는 map()을 사용한다. 각 항목에 고유한 key를 반드시 지정해야 한다.
const FruitList = () => { const fruits = ['사과', '바나나', '오렌지', '포도']; return ( <ul> {fruits.map((fruit, index) => ( <li key={index}>{fruit}</li> ))} </ul> ); }; export default FruitList;
JavaScript
복사
key는 React가 어떤 항목이 변경됐는지 추적하기 위해 필요하다. 가능하면 인덱스 대신 고유한 ID를 사용하는 것이 좋다.

useEffect로 사이드 이펙트 처리하기

API 호출, 타이머 설정, 이벤트 리스너 등록처럼 렌더링과 무관한 작업은 useEffect에서 처리한다.
import { useState, useEffect } from 'react'; const Timer = () => { const [seconds, setSeconds] = useState(0); useEffect(() => { // 컴포넌트 마운트 시 타이머 시작 const interval = setInterval(() => { setSeconds((prev) => prev + 1); }, 1000); // 컴포넌트 언마운트 시 타이머 정리 return () => clearInterval(interval); }, []); // 빈 배열 = 마운트 시 한 번만 실행 return <p>경과 시간: {seconds}</p>; }; export default Timer;
JavaScript
복사
의존성 배열([])이 비어 있으면 마운트 시 한 번만 실행된다. 특정 값이 바뀔 때마다 실행하려면 그 값을 배열에 넣으면 된다.

전체 예제 코드

지금까지 다룬 내용을 하나로 합친 실습용 코드다. Vite 프로젝트의 App.jsx에 붙여넣으면 바로 실행된다.
import { useState, useEffect } from 'react'; // 할일 아이템 컴포넌트 const TodoItem = ({ todo, onDelete }) => { return ( <li style={{ marginBottom: '8px' }}> <span>{todo.text}</span> <button onClick={() => onDelete(todo.id)} style={{ marginLeft: '8px', color: 'red' }} > 삭제 </button> </li> ); }; // 메인 앱 컴포넌트 const App = () => { const [todos, setTodos] = useState([ { id: 1, text: 'React 기초 공부하기' }, { id: 2, text: '컴포넌트 만들어보기' }, ]); const [input, setInput] = useState(''); const [count, setCount] = useState(0); // 할일 개수 추적 useEffect(() => { setCount(todos.length); }, [todos]); const addTodo = () => { if (!input.trim()) return; const newTodo = { id: Date.now(), text: input }; setTodos((prev) => [...prev, newTodo]); setInput(''); }; const deleteTodo = (id) => { setTodos((prev) => prev.filter((todo) => todo.id !== id)); }; return ( <div style={{ maxWidth: '480px', margin: '40px auto', fontFamily: 'sans-serif' }}> <h1>할일 목록</h1> <p>{count}개의 할일이 있습니다.</p> <div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}> <input type="text" value={input} onChange={(e) => setInput(e.target.value)} placeholder="할일을 입력하세요" style={{ flex: 1, padding: '8px' }} onKeyDown={(e) => e.key === 'Enter' && addTodo()} /> <button onClick={addTodo} style={{ padding: '8px 16px' }}>추가</button> </div> <ul style={{ listStyle: 'none', padding: 0 }}> {todos.map((todo) => ( <TodoItem key={todo.id} todo={todo} onDelete={deleteTodo} /> ))} </ul> </div> ); }; export default App;
JavaScript
복사

마치며

React의 핵심은 결국 컴포넌트상태다. 컴포넌트로 UI를 나누고, 상태가 바뀌면 화면이 자동으로 업데이트된다는 원리만 이해하면 나머지는 자연스럽게 따라온다. 처음에는 useStateuseEffect 두 가지 훅만 익혀도 웬만한 기능은 다 만들 수 있다.

최종 결과

위 전체 예제 코드를 실행하면 아래와 같은 할일 목록 앱이 완성된다.
상단 입력창에 할일을 작성하고 추가 버튼(또는 Enter)을 누르면 목록에 추가된다.
각 항목 우측의 삭제 버튼을 누르면 해당 항목이 제거된다.
상단의 총 개수는 useEffect로 실시간 추적된다.
주요 React 개념이 이 앱 하나에 모두 담겨 있다. TodoItem 컴포넌트(컴포넌트 분리), todos 상태(useState), 할일 개수 추적(useEffect), 입력 이벤트(onChange), 조건부 필터링(filter)이 유기적으로 연결되어 작동한다.