React 기초 핵심 가이드 — 컴포넌트·Props·State를 초보자 눈높이로 이해하기
Next.js를 시작하기 전에, 잠깐 멈춰 서야 하는 이유
요즘 웹 개발 생태계의 대세는 단연 Next.js입니다. 서버 사이드 렌더링(SSR), 검색 엔진 최적화(SEO), 자동 이미지 최적화 등 매력적인 기능들로 가득하죠. 하지만 HTML과 CSS, 기초 자바스크립트만 갓 뗀 상태에서 곧바로 Next.js 튜토리얼을 따라 하다 보면 금세 거대한 벽에 부딪히게 됩니다.
"Next.js는 사실 React(리액트)라는 뼈대 위에 지어진 집"이기 때문입니다. React의 핵심 멘탈 모델을 이해하지 못한 채 Next.js 코드를 복사해서 붙여넣기만 하면, 작은 에러 하나만 발생해도 어디서부터 고쳐야 할지 막막해집니다.
오늘은 React를 지탱하는 가장 기본적이면서도 중요한 세 가지 기둥인 컴포넌트(Component), 프롭스(Props), 그리고 스테이트(State)를 초보자의 눈높이에 맞춰 완벽하게 정리해 드리겠습니다.
1. 컴포넌트 (Component): 화면을 조립하는 레고 블록
기존의 전통적인 웹 페이지(HTML)는 하나의 거대한 도화지에 모든 내용을 위에서 아래로 순서대로 적는 방식이었습니다. 웹사이트에 버튼이 50개 있다면, 그 50개의 버튼 태그를 일일이 다 써줘야 했죠. 만약 버튼의 디자인이나 문구를 수정해야 한다면 50군데를 일일이 찾아다니며 고쳐야 했습니다.
React는 이 비효율을 해결하기 위해 화면을 재사용 가능한 '부품(컴포넌트)' 단위로 쪼개어 생각합니다. 마치 자동차 공장에서 바퀴, 핸들, 엔진을 각각 따로 만든 뒤 조립하는 것과 같죠.
컴포넌트는 사실 평범한 자바스크립트 함수입니다
React에서 컴포넌트를 만드는 법은 놀라울 정도로 단순합니다. 첫 글자를 대문자로 시작하는 자바스크립트 함수를 만들고, 화면에 보여줄 JSX(HTML과 유사한 문법)를 return하기만 하면 됩니다.
// Button이라는 이름의 나만의 부품 만들기
function CustomButton() {
return (
<button className="btn-primary">
클릭해주세요!
</button>
);
}
// 다른 화면에서 이 부품을 레고처럼 가져다 쓰기
export default function App() {
return (
<div>
<h1>방장 블로그에 오신 것을 환영합니다</h1>
<CustomButton />
<CustomButton />
</div>
);
}
이렇게 정의해두면 <CustomButton />이라는 태그를 원하는 곳에 100번이고 1,000번이고 가져다 쓸 수 있습니다. 수정할 때도 CustomButton 함수 한 곳만 고치면 모든 버튼이 일괄 업데이트됩니다.
2. Props: 부모 부품이 자식 부품에게 주는 선물 상자
그런데 모든 버튼이 똑같은 글씨와 똑같은 색상이라면 쓸모가 없겠죠? 어떤 버튼은 "로그인", 어떤 버튼은 "회원가입", 또 어떤 버튼은 "삭제"라고 적혀 있어야 합니다.
이때 사용하는 것이 바로 Props(Properties의 줄임말)입니다. 부모 컴포넌트가 자식 컴포넌트를 호출할 때 원하는 데이터(옵션)를 전달해주는 메커니즘입니다.
// text와 color라는 속성을 선물로 전달받는 버튼
function ActionButton({ text, color }) {
return (
<button style={{ backgroundColor: color, color: '#fff', padding: '10px 20px', borderRadius: '6px' }}>
{text}
</button>
);
}
// 부모 컴포넌트에서 각각 다른 옵션을 건네주기
export default function Page() {
return (
<div>
<ActionButton text="로그인" color="#3b82f6" />
<ActionButton text="회원가입" color="#10b981" />
<ActionButton text="계정 삭제" color="#ef4444" />
</div>
);
}
Props를 쓸 때 반드시 기억해야 할 철칙이 하나 있습니다. 바로 "단방향 데이터 흐름"입니다. Props는 오직 부모에서 자식으로만 내려가며, 자식 컴포넌트 안에서 전달받은 Props를 제멋대로 수정할 수 없습니다(Read-Only). 이 엄격한 규칙 덕분에 수만 줄의 코드에서도 데이터가 어디서 와서 어디로 흘러가는지 추적하기가 아주 쉬워집니다.
3. State: 화면을 살아 숨쉬게 만드는 '내부 상태'
Props가 외부(부모)에서 주어지는 고정된 값이라면, State는 컴포넌트 스스로가 기억하고 변경할 수 있는 '살아있는 데이터'입니다.
일반 자바스크립트 변수(let count = 0;)와 State의 결정적인 차이점은 무엇일까요? 일반 변수는 값이 바뀌어도 브라우저 화면이 다시 그려지지 않습니다. 하지만 React의 State가 변경되면, React는 "어라? 데이터가 바뀌었네? 바뀐 데이터에 맞게 화면을 다시 그려야지!" 하고 스스로 해당 컴포넌트를 다시 렌더링(Re-render)합니다.
useState 훅으로 상태 관리하기
import { useState } from 'react';
function LikeCounter() {
// count: 현재 상태 값
// setCount: 상태를 바꿀 때 부르는 전용 함수
// useState(0): 초깃값은 0
const [likes, setLikes] = useState(0);
function handleLikeClick() {
setLikes(likes + 1);
}
return (
<div style={{ textAlign: 'center', padding: '20px' }}>
<p>현재 좋아요 수: <strong>{likes}</strong>개</p>
<button onClick={handleLikeClick}>
👍 좋아요 누르기
</button>
</div>
);
}
버튼을 누를 때마다 setLikes 함수가 호출되고, React는 화면의 {likes} 숫자만 마법처럼 즉각 1씩 증가시켜 줍니다. 복잡하게 DOM을 직접 조작(document.getElementById)할 필요가 전혀 없는 것이죠.
초보자가 가장 많이 범하는 실수 3가지
- State를 직접 수정하려고 하는 것:
likes = likes + 1;처럼 쓰면 React가 값의 변화를 감지하지 못합니다. 반드시 전용 세터 함수인setLikes(...)를 사용해야 합니다. - 컴포넌트 이름 첫 글자를 소문자로 짓는 것:
function myButton()처럼 소문자로 쓰면 React는 이를 HTML 내장 태그(div, p 같은)로 착각하여 오류를 일으킵니다. 반드시MyButton처럼 대문자로 시작하세요. - 불필요하게 모든 변수를 State로 만드는 것: 단순히 계산으로 도출할 수 있는 값(예:
const fullName = firstName + lastName;)은 굳이 State로 만들지 마세요. 불필요한 재렌더링이 발생하여 성능이 저하됩니다.
마치며: Next.js로 나아갈 준비 완료
정리해 볼까요?
- Component: 재사용 가능한 UI 블록 (레고 블록)
- Props: 부모가 자식에게 건네는 설정 데이터 (단방향)
- State: 컴포넌트 내부에서 바뀌며 화면을 다시 그리게 하는 상태 값



댓글
댓글을 불러오는 중...