React 컴포넌트는 props
를 사용해 부모에서 자식으로 데이터를 전달합니다. props
는 HTML의 속성과 유사하지만, 객체, 배열, 함수 등 모든 JavaScript 값을 전달할 수 있습니다. 이를 통해 컴포넌트 간 데이터를 효율적으로 공유할 수 있습니다.
props
는 JSX 태그에 정보를 전달하는 속성입니다. 아래는 기본적인 예시입니다:
function Avatar() {
return (
<img
className="avatar"
src="https://i.imgur.com/1bX5QH6.jpg"
alt="Lin Lanying"
width={100}
height={100}
/>
);
}
export default function Profile() {
return <Avatar />;
}
위 코드에서 <img>
태그에 전달된 className
, src
, alt
등의 값은 HTML 표준 속성이며, React 컴포넌트에서도 그대로 사용할 수 있습니다.
React에서는 두 단계를 통해 부모 컴포넌트에서 자식 컴포넌트로 props
를 전달하고 사용할 수 있습니다.
부모 컴포넌트에서 props
를 사용해 데이터를 전달합니다. 예를 들어, Avatar
컴포넌트에 person
객체와 size
값을 전달합니다.
export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}
💡 주의:
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
와 같이 이중 중괄호({{ }}
)는 JSX에서 객체를 표현하기 위한 문법입니다.
전달받은 props
는 함수 매개변수로 받아 사용할 수 있습니다. 보통 구조 분해 할당을 사용해 간단히 작성합니다.
function Avatar({ person, size }) {
return (
<img
className="avatar"
src={`https://i.imgur.com/${person.imageId}.jpg`}
alt={person.name}
width={size}
height={size}
/>
);
}
props
를 사용하는 전체 예시는 아래와 같습니다:
function Avatar({ person, size }) {
return (
<img
className="avatar"
src={`https://i.imgur.com/${person.imageId}.jpg`}
alt={person.name}
width={size}
height={size}
/>
);
}
export default function Profile() {
return (
<div>
<Avatar person={{ name: 'Katsuko Saruhashi', imageId: 'YfeOqp2' }} size={100} />
<Avatar person={{ name: 'Aklilu Lemma', imageId: 'OKS67lh' }} size={80} />
<Avatar person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }} size={50} />
</div>
);
}
컴포넌트에 props
가 전달되지 않았을 경우, 기본값을 설정할 수 있습니다. 기본값은 구조 분해 할당을 활용해 설정합니다.
function Avatar({ person, size = 100 }) {
return (
<img
className="avatar"
src={`https://i.imgur.com/${person.imageId}.jpg`}
alt={person.name}
width={size}
height={size}
/>
);
}
위 코드에서 size
가 전달되지 않을 경우, 기본값으로 100
이 사용됩니다. 그러나 size={0}
또는 size={null}
로 전달된다면 기본값은 사용되지 않습니다.
독립적인 컴포넌트 구성:
부모 컴포넌트와 자식 컴포넌트를 독립적으로 관리할 수 있습니다.
props
를 통해 데이터만 전달합니다.props
를 활용해 렌더링합니다.함수의 매개변수처럼 활용:
props
는 함수의 매개변수와 동일하게 동작합니다. 모든 컴포넌트는 하나의 props
객체를 매개변수로 받습니다.
function Avatar(props) {
const { person, size } = props;
// 또는
let person = props.person;
let size = props.size;
}
💡 주의:
props
를 구조 분해 할 때, 반드시 중괄호({}
)로 감싸야 합니다:
function Avatar({ person, size }) { ... }
React에서 props
는 컴포넌트 간 데이터를 주고받는 중요한 역할을 합니다. 함수의 인수처럼 동작하며, 컴포넌트를 독립적이고 재사용 가능하게 만듭니다. 이를 통해 유지보수성과 확장성을 높일 수 있습니다.
React 컴포넌트는 조건에 따라 다른 UI를 표시할 수 있습니다. 자주 사용되는 조건부 렌더링 방법은 다음과 같습니다:
if
문&&
)? :
)return
하기if
문 사용function Item({ name, isPacked }) {
if (isPacked) {
return <li className="item">{name} ✔</li>;
}
return <li className="item">{name}</li>;
}
null
을 사용하여 아무것도 렌더링하지 않기컴포넌트에서 아무것도 렌더링하지 않으려면 null
을 반환할 수 있습니다:
function Item({ name, isPacked }) {
if (isPacked) {
return null;
}
return <li className="item">{name}</li>;
}
중복된 코드를 줄이고 가독성을 높이기 위해 조건부를 JSX에 포함할 수 있습니다.
? :
)삼항 연산자를 사용하면 코드가 더 간결해집니다:
return (
<li className="item">
{isPacked ? name + ' ✔' : name}
</li>
);
복잡한 조건부는 자식 컴포넌트로 추출하여 코드의 가독성을 높이세요.
&&
)조건이 참일 때만 특정 JSX를 렌더링하려면 &&
를 사용할 수 있습니다:
return (
<li className="item">
{name} {isPacked && '✔'}
</li>
);
조건부 로직이 복잡할 경우, JSX를 변수에 저장하여 사용하세요:
function Item({ name, isPacked }) {
let itemContent = name;
if (isPacked) {
itemContent = <del>{name + ' ✔'}</del>;
}
return <li className="item">{itemContent}</li>;
}
💡 TIP: JavaScript 문법을 잘 활용하면 React 컴포넌트뿐만 아니라 모든 JS 코드의 가독성과 유지보수성을 높일 수 있습니다!
리액트에서 유사한 컴포넌트를 여러 개 표시하고 싶을 때, map()
과 filter()
를 활용해 데이터를 필터링하고 컴포넌트 배열로 변환하여 렌더링할 수 있습니다.
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
export default function List() {
const listItems = people.map(person =>
<li>{person}</li>
);
return <ul>{listItems}</ul>;
}
위 코드는 아래와 같이 렌더링됩니다.
<ul>
<li>Creola Katherine Johnson: mathematician</li>
<li>Mario José Molina-Pasquel Henríquez: chemist</li>
<li>Mohammad Abdus Salam: physicist</li>
<li>Percy Lavon Julian: chemist</li>
<li>Subrahmanyan Chandrasekhar: astrophysicist</li>
</ul>
map()
과 filter()
로 리스트 렌더링map()
을 사용하여 리스트를 렌더링할 때, 각 항목을 CourseItem
컴포넌트로 전달할 수 있습니다.
import Card from '../Card';
import CourseItem from './CourseItem'
function CourseListCard({ items }) {
return (
<Card title='강의 목록'>
<div className='courses'>
{items.map(item => <CourseItem {...item} />)}
</div>
</Card>
);
}
export default CourseListCard;
filter()
를 사용해 조건에 맞는 항목만 렌더링할 수도 있습니다.
import './App.css';
import CourseListCard from './components/course/CourseListCard';
function App() {
const items = [
{ title: '입문자를 위한 HTML&CSS', isFavorite: false },
{ title: '자바스크립트 입문', isFavorite: true },
{ title: '포트폴리오 사이트 만들기', isFavorite: true },
];
const favoriteItems = items.filter(item => item.isFavorite);
return (
<main style={{ flexDirection: 'column', gap: '1rem' }}>
<CourseListCard title="강의 목록" items={items} />
<CourseListCard title="관심 강의" items={favoriteItems} />
</main>
);
}
export default App;
key
를 사용해 리스트 항목을 고유하게 만들기리스트 항목에 key
를 사용해야 경고 없이 효율적으로 렌더링할 수 있습니다.
<li key={item.id}>{item.title}</li>
key
는 각 컴포넌트가 배열의 항목을 고유하게 식별할 수 있도록 도와줍니다.
여러 개의 DOM 노드를 렌더링할 때는 Fragment
를 사용하여 key
를 전달할 수 있습니다.
import { Fragment } from 'react';
const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);
Fragment
는 실제 DOM에 나타나지 않으며, 여러 엘리먼트를 묶어서 하나의 키를 전달할 수 있게 해줍니다.
이벤트 핸들러는 사용자가 마우스를 클릭하거나 입력 상자에서 키보드를 타이핑하는 등 사용자와의 상호작용에 따라 실행되는 사용자 정의 함수입니다. React에서는 JSX에 이벤트 핸들러를 추가할 수 있습니다.
이벤트 핸들러를 추가하려면 먼저 함수를 정의하고 이를 JSX 태그에 props 형태로 전달해야 합니다. 아래 예시는 아무런 동작도 수행하지 않는 버튼입니다.
// App.jsx
export default function Button() {
return (
<button>
I don't do anything
</button>
);
}
이제 다음 3단계 과정을 거쳐 사용자가 버튼을 클릭할 때 메시지를 보여주도록 만들어 보겠습니다.
Button
컴포넌트 내부에 handleClick
함수를 선언합니다.alert
를 사용합니다.<button>
JSX에 onClick={handleClick}
을 추가합니다.export default function Button() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
handleClick
함수를 정의하고 이를 <button>
에 prop 형태로 전달하였습니다. 여기서 handleClick
은 이벤트 핸들러입니다. 이벤트 핸들러 함수는 다음과 같은 특징을 가집니다.
handle
로 시작하고 그 뒤에 이벤트명을 붙인 함수명을 가집니다.관습적으로 handle
로 시작하여 이벤트명을 이어 붙인 이벤트 핸들러 명명법이 일반적입니다. onClick={handleClick}
, onMouseEnter={handleMouseEnter}
와 같은 경우를 자주 볼 수 있습니다.
다른 방법으로 이벤트 핸들러를 JSX 내에서 인라인으로 정의할 수도 있습니다.
<button onClick={function handleClick() { alert('You clicked me!');}}>
또는 화살표 함수를 사용하여 보다 간결하게 정의할 수도 있습니다.
<button onClick={() => { alert('You clicked me!');}}>
이러한 스타일은 모두 동일한 결과를 보여줍니다. 특히 인라인 이벤트 핸들러는 짧은 함수들을 정의할 때 편리합니다.
함수를 전달하기 (올바른 예시) | 함수를 호출하기 (잘못된 예시) |
---|---|
<button onClick={handleClick}> | <button onClick={handleClick()}> |
handleClick
함수는 onClick
이벤트 핸들러에 전달되었습니다. 이후 React는 이 내용을 기억하고 오직 사용자가 버튼을 클릭했을 때만 함수를 호출하도록 합니다. 두 번째 예시에서는 handleClick()
끝의 ()
가 렌더링 과정 중 클릭이 없었음에도 불구하고 즉시 함수를 실행하도록 만듭니다. 이는 JSX {
와 }
내의 자바스크립트가 즉시 실행되기 때문입니다. 인라인으로 코드를 작성할 때에도 동일한 함정이 다른 형태로 나타납니다.
함수를 전달하기 (올바른 예시) | 함수를 호출하기 (잘못된 예시) |
---|---|
<button onClick={() => alert('...')}> | <button onClick={alert('...')}> |
// 이 alert는 클릭 시 실행되지 않고 컴포넌트가 렌더링 된 시점에 실행됩니다!
<button onClick={alert('You clicked me!')}>
만약 이벤트 핸들러를 인라인으로 정의하고자 한다면, 아래와 같이 익명 함수로 감싸면 됩니다.<button onClick={() => alert('You clicked me!')}>
이러한 방법으로 매 렌더링마다 내부 코드를 실행하지 않고 함수를 생성하여 추후 이벤트에 의해 호출되게 합니다. 두 가지 경우 모두, 전달하는 것은 함수입니다.<button onClick={handleClick}>
은 handleClick
함수를 전달합니다.
<button onClick={() => alert('...')}>
은 () => alert('...')
함수를 전달합니다.
이벤트 핸들러는 컴포넌트 내부에서 선언되기에, 이들은 해당 컴포넌트의 prop에 접근할 수 있습니다. 아래에서 클릭 시 message
prop의 내용을 포함한 alert를 표시하는 버튼을 볼 수 있습니다.
function AlertButton({ message, children }) {
return (
<button onClick={() => alert(message)}>
{children}
</button>
);
}
export default function Toolbar() {
return (
<div>
<AlertButton message="Playing!">
Play Movie
</AlertButton>
<AlertButton message="Uploading!">
Upload Image
</AlertButton>
</div>
);
}
위와 같이 두 개의 버튼이 서로 다른 메시지를 표시할 수 있습니다. 전달되는 메시지를 변경해보세요.
종종 부모 컴포넌트로 자식의 이벤트 핸들러를 지정하기를 원할 수 있습니다. 버튼의 경우를 고려해 봅시다. Button
컴포넌트를 사용하는 위치에 따라 다른 기능을 수행하도록 만들고자 할 때가 있을 것입니다. 한 버튼은 영화를 재생하고 다른 버튼은 이미지를 업로드하도록 말이죠.
이러한 기능을 위해서 컴포넌트가 그 부모 컴포넌트로부터 받은 prop을 이벤트 핸들러로 다음과 같이 전달합니다.
function Button({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
);
}
function PlayButton({ movieName }) {
function handlePlayClick() {
alert(`Playing ${movieName}!`);
}
return (
<Button onClick={handlePlayClick}>
Play "{movieName}"
</Button>
);
}
function UploadButton() {
return (
<Button onClick={() => alert('Uploading!')}>
Upload Image
</Button>
);
}
export default function Toolbar() {
return (
<div>
<PlayButton movieName="Kiki's Delivery Service" />
<UploadButton />
</div>
);
}
위 코드에서는 Toolbar
컴포넌트가 PlayButton
과 UploadButton
을 렌더링합니다.
PlayButton
은 handlePlayClick
을 Button
내 onClick
prop으로 전달합니다.UploadButton
은 () => alert('Uploading!')
을 Button
내 onClick
prop으로 전달합니다.최종적으로, Button
컴포넌트는 onClick
prop을 받습니다. 이후 받은 prop을 브라우저 빌트인 <button>
의 onClick={onClick}
으로 직접 전달합니다. 이를 통해 React가 전달받은 함수를 클릭 시점에 호출함을 알 수 있습니다.
만약 디자인 시스템을 적용한다면 버튼과 같은 컴포넌트는 동작을 지정하지 않고 스타일만 지정하는 것이 일반적입니다. 그 대신, PlayButton
과 UploadButton
같은 컴포넌트가 이벤트 핸들러를 전달하도록 합니다.
onClick 과 같은 브라우저 이벤트 이름에 맞게 사전세 이벤트 핸들러 prop이 등록되어 있는 걸 확인할 수 있고요.
우리가 작성한 사용자 정의 컴포넌트의 이벤트 핸들러 prop은 원하는 대로 이름을 지을 수 있습니다.
<button>
과 <div>
같은 빌트인 컴포넌트는 onClick
과 같은 브라우저 이벤트 이름만을 지원합니다. 그러나 사용자 정의 컴포넌트에서는 이벤트 핸들러 prop의 이름을 원하는 대로 명명할 수 있습니다.
관습적으로 이벤트 핸들러 prop을 지을 때:
onClick
, onMouseEnter
처럼 브라우저 이벤트를 그대로 따릅니다.handleClick
과 같은 형식으로 정의합니다.