React 컴포넌트는 JSX를 반환하는 자바스크립트 함수입니다. 버튼, 카드, 페이지 전체처럼 재사용 가능한 UI 조각입니다. HTML 태그처럼 <Profile />로 사용하며, React는 화면에 무엇을 놓을지 알아내기 위해 그 함수를 호출합니다. 앱은 컴포넌트 안에 다른 컴포넌트를 넣어서 만듭니다.
App은 Profile을 두 번 렌더링하고, 각 Profile은 Avatar를 렌더링합니다. Avatar 안의 이니셜을 바꾸면 두 프로필이 모두 업데이트됩니다. 하나의 정의를 공유하기 때문입니다. 지금은 이 목록의 모든 사람이 똑같습니다. 다음 페이지인 props에서 각 복사본에 자기만의 데이터를 넘기는 방법을 보여 줍니다.
컴포넌트 작성하기
함수 컴포넌트에는 두 가지 요구 사항이 있습니다.
- 이름이 대문자로 시작합니다:
profile이 아니라Profile입니다. - React가 렌더링할 수 있는 것을 반환합니다: JSX, 문자열, 숫자,
null(아무것도 렌더링하지 않음), 또는 이것들의 배열입니다.
function Welcome() {
return <h1>Welcome back</h1>;
}
// Arrow functions work the same way
const Welcome = () => <h1>Welcome back</h1>;
JSX가 여러 줄에 걸쳐 있으면 return 뒤에 괄호로 감싸세요. 괄호가 없으면 return 뒤에 줄바꿈이 오는 순간 undefined가 반환되고, 그 뒤의 모든 것은 무시됩니다.
이름을 대문자로 시작해야 하는 이유
JSX는 태그의 첫 글자를 봅니다. 소문자는 내장 HTML 요소를 뜻하므로 <div>는 <div>로 남습니다. 대문자는 "스코프 안의 변수"를 뜻하므로 <Profile />은 직접 만든 Profile 함수를 호출합니다. 컴포넌트 이름을 소문자로 지으면 React는 대신 그 이름의 알 수 없는 HTML 요소를 만듭니다.
두 번째 줄에는 라벨만 보이고, 콘솔에는 Greeting ran만 나타납니다. React는 빈 <greeting> 태그를 페이지에 넣었고 소문자 함수는 전혀 호출하지 않았습니다. 개발 빌드에서는 The tag <greeting> is unrecognized in this browser라는 경고도 나오지만, 미리보기는 프로덕션 빌드처럼 실행되므로 빠진 텍스트만 보입니다. 함수 이름을 Greetings로, 태그를 <Greetings />로 바꾸면 해결됩니다.
컴포넌트 조합하기
컴포넌트는 중첩하라고 만든 것입니다. 페이지는 트리입니다. 맨 위에 App이 있고, 그 아래 레이아웃 조각들, 다시 그 아래 버튼 하나 같은 더 작은 부분이 있습니다. 각 컴포넌트는 자기가 직접 렌더링하는 조각만 알기 때문에 한 번에 하나씩 생각할 수 있습니다.
각 LikeButton은 자기만의 상태를 가지므로 게시물 하나에 좋아요를 눌러도 다른 게시물은 그대로입니다. 모든 게시물에 공유 버튼을 추가하려면 한 번만 작성하고 Post 안에 <ShareButton />을 추가하면 됩니다.
컴포넌트를 나눠야 할 때
정해진 규칙은 없지만, 어떤 조각을 별도 컴포넌트로 만들 만하다는 신호가 몇 가지 있습니다. 같은 마크업이 두 곳에 나타날 때, JSX의 한 부분이 자기만의 상태를 가질 때(위의 좋아요 버튼처럼), 또는 함수가 길어져서 return을 보려면 스크롤해야 할 때입니다. 이런 기준으로 나누면 각 컴포넌트를 한 화면 안에서 읽을 수 있을 만큼 작게 유지할 수 있습니다. 너무 일찍 나눠도 손해는 적습니다. 컴포넌트는 함수일 뿐이라서 둘을 다시 합치기 쉽습니다.
컴포넌트 내보내기와 가져오기
여기 에디터는 파일 하나만 담고 있지만, 실제 프로젝트에서는 컴포넌트를 각자의 파일에 둡니다. 파일 하나에는 default export 하나와 원하는 만큼의 named export가 있을 수 있습니다.
// Avatar.jsx
export default function Avatar({ initials }) {
return <div className="avatar">{initials}</div>;
}
// Badge.jsx
export function Badge({ label }) {
return <span className="badge">{label}</span>;
}
export function NewBadge() {
return <Badge label="new" />;
}
// Profile.jsx
import Avatar from './Avatar.jsx';
import { Badge, NewBadge } from './Badge.jsx';
export default function Profile() {
return (
<div>
<Avatar initials="AL" />
<Badge label="admin" />
<NewBadge />
</div>
);
}
default export는 어떤 이름으로든 import할 수 있고, named export는 정확한 이름을 중괄호 안에 넣어 import합니다. 보통 팀에서 한 가지 스타일을 정해 꾸준히 씁니다. 파일 이름은 흔히 컴포넌트와 맞춥니다: Avatar.jsx가 Avatar를 export합니다.
컴포넌트를 순수하게 유지하기
React는 컴포넌트가 렌더링하는 동안 순수 함수처럼 동작하기를 기대합니다. 같은 props와 상태는 같은 JSX를 만들고, 렌더링은 실행 전에 이미 있던 것을 아무것도 바꾸지 않아야 합니다. React는 직접 제어할 수 없는 시점에 컴포넌트를 여러 번 렌더링할 수 있으므로, 렌더링 중에 바깥 변수를 수정하는 컴포넌트는 매번 다른 결과를 만듭니다.
순수하지 않은 컵은 1, 2, 3으로 시작합니다. 버튼을 클릭해 보세요. 손님에 대해서는 아무것도 바뀌지 않았는데, 순수하지 않은 목록은 이제 4, 5, 6이 됩니다. 렌더링할 때마다 공유된 guest 변수에 값을 더했기 때문입니다. 순수한 컵은 props에서 번호를 받으므로 올바르게 유지됩니다. 개발 환경에서는 Strict Mode가 바로 이런 버그를 드러내기 위해 모든 컴포넌트를 두 번 렌더링합니다. 미리보기는 프로덕션 빌드처럼 실행되므로 여기서는 다시 렌더링할 때만 어긋남이 보입니다.
무언가를 바꾸는 것은 괜찮습니다. 렌더링 중에만 하지 않으면 됩니다. 상태는 이벤트 핸들러에서 업데이트하고, 바깥 세계(타이머, 구독, 문서 제목)와의 소통은 이펙트에서 하세요.
컴포넌트와 요소
컴포넌트는 함수 Profile입니다. 요소는 <Profile />이라고 쓸 때 JSX가 만드는 것으로, "이 props로 Profile을 렌더링하라"는 작은 객체입니다. React는 요소를 만들고, 요소가 가리키는 컴포넌트를 호출하고, 그 결과를 DOM으로 바꿉니다. 컴포넌트를 Profile()처럼 직접 호출하지 마세요. <Profile />이라고 쓰고 React가 호출하게 해야 React가 그 상태와 트리 안의 위치를 추적할 수 있습니다.
그 위치가 중요합니다. 이 예제는 컴포넌트 안에 다른 컴포넌트를 정의하는데, 흔히 하는 실수입니다.
무언가를 입력한 다음 버튼을 클릭해 보세요. 입력한 텍스트가 사라집니다. App이 렌더링될 때마다 완전히 새로운 NameField 함수가 만들어지므로, React는 그 자리에서 다른 컴포넌트 타입을 보게 되고 이전 컴포넌트를 상태와 함께 버립니다. NameField를 App 위, 파일의 최상위로 옮기면 클릭할 때마다 텍스트가 유지됩니다. 렌더링 페이지에서 React가 무엇을 유지할지 어떻게 결정하는지 설명합니다.
함수 컴포넌트와 클래스 컴포넌트
React 16.8에서 훅이 등장하기 전에는 상태가 필요한 컴포넌트를 클래스로 작성했습니다.
import { Component } from 'react';
class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
클래스 컴포넌트는 React 19에서도 여전히 동작하며, 오래된 코드베이스와 지금도 클래스가 필요한 에러 바운더리에서 보게 됩니다. 새 코드는 useState, useEffect 같은 훅과 함께 함수 컴포넌트를 사용하며, 같은 일을 더 적은 코드로 해냅니다. 클래스 컴포넌트 페이지에서 클래스의 각 기능을 대응하는 훅에 연결해 보여 줍니다.
자주 묻는 질문
React에서 컴포넌트란 무엇인가요?
컴포넌트는 JSX를 반환하는 자바스크립트 함수로 작성한, 재사용 가능한 UI 조각입니다. HTML 태그처럼 <Profile />로 사용하며, React는 무엇을 그릴지 알아내기 위해 그 함수를 호출합니다.
React 컴포넌트 이름은 왜 대문자로 시작해야 하나요?
JSX는 첫 글자로 태그가 무엇인지 판단합니다. <profile /> 같은 소문자 태그는 profile이라는 HTML 요소로 취급되고, 직접 만든 함수는 호출되지 않습니다. <Profile /> 같은 대문자 태그가 컴포넌트를 가리킵니다.
함수 컴포넌트와 클래스 컴포넌트의 차이는 무엇인가요?
둘 다 UI를 설명합니다. 함수 컴포넌트는 일반 함수이며 상태와 이펙트에 훅을 사용합니다. 클래스 컴포넌트는 React.Component를 상속하고 render, componentDidMount 같은 메서드를 사용합니다. 새 코드는 함수 컴포넌트를 씁니다.
React에서 순수 컴포넌트란 무엇인가요?
순수 컴포넌트는 같은 props와 상태에 대해 같은 JSX를 반환하고, 렌더링하는 동안 자기 바깥의 것을 아무것도 바꾸지 않습니다. React는 이 성질에 기대어 필요한 만큼 자주 컴포넌트를 렌더링해도 예상치 못한 일이 생기지 않게 합니다.
컴포넌트 안에 다른 컴포넌트를 정의해도 되나요?
피하세요. 다른 컴포넌트 안에서 정의한 컴포넌트는 렌더링할 때마다 새 함수가 되므로, React는 매번 다른 컴포넌트로 취급하고 상태를 초기화합니다. 컴포넌트는 파일의 최상위에서 정의하세요.