리액트에서 부모 컴포넌트가 자식 컴포넌트에 데이터를 전달할 때 사용하는 것이 Props이다.
(ref와 key 속성은 예외적으로 전달할 수 없었지만, 19버전에서 ref는 전달받게 변경되었다.)
Props 객체는 컴포넌트를 HTML 태그처럼 사용해 값을 속성 형태로 전달한다.
여러 데이터를 전달하고 싶을 때는 속성을 여러 개 추가하면 된다.
function Car(props) {
return <h2>I am a {props.brand}!</h2>;
}
function Garage() {
return (
<>
<h1>Who lives in my garage?</h1>
<Car brand="Ford" />
</>
);
}
function Car(props) {
return <h2>I am a {props.brand}!</h2>;
}
function Garage() {
const carName = "Ford";
return (
<>
<h1>Who lives in my garage?</h1>
<Car brand={carName} />
</>
);
}
데이터마다 개별 props로 지정할 수 있지만, 데이터 개수가 많다면 배열이나 객체로 묶어 전달하는 것이 깔끔하고 효율적이다.
function Car(props) {
// props.brand가 객체이므로 내부 속성에 접근
return <h2>I am a {props.brand.model}!</h2>;
}
function Garage() {
const carInfo = { name: "Ford", model: "Mustang" };
return (
<>
<h1>Who lives in my garage?</h1>
<Car brand={carInfo} />
</>
);
}
props는 단일 객체이므로 부모 컴포넌트에 속성이 몇십 개 지정되어도 결국 하나의 props 객체로 전달된다.
이때 구조 분해 할당을 이용하면 객체의 속성을 더 간단하게 변수에 할당할 수 있다.
function MyCom(props) {
return <div>{props.name}</div>;
}
장점: 어떤 props가 오든 한 함수 시그니처로 모두 접근 가능
단점: 매번 props.xxx로 접근해야 해서 장황해질 수 있음
props가 객체이므로, 필요한 속성 이름만 뽑아 바로 변수로 받을 수 있다.
React에서 이 패턴은 매우 자주 쓰이며, 컴포넌트가 어떤 props를 사용하는지 시그니처만 봐도 한눈에 드러나기 때문에 가독성이 올라간다.
function MyCom({ name }) {
return <div>{name}</div>;
}
장점: 필요한 값만 변수로 바로 꺼내서 간결
단점: 어떤 prop을 쓰는지 시그니처에 명시해야 함
주의:
({props})는 "props라는 이름의 prop"을 구조 분해하려는 문법이므로 보통 의도와 다르다. 대부분은{ name, age }처럼 개별 prop 이름을 직접 꺼낸다.
Props가 전달되지 않았을 때 사용할 기본값을 설정할 수 있다.
function MyComponent({ name = "Guest", age = 0 }) {
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
</div>
);
}
// 사용
<MyComponent /> // Name: Guest, Age: 0
<MyComponent name="John" /> // Name: John, Age: 0
function App() {
return (
<MyComponent
name="John Doe"
age={30}
email="john@example.com"
address="123 Main St"
/>
);
}
function MyComponent({ name, age, email }) {
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
}
객체의 모든 속성을 props로 펼쳐서 전달할 수 있다.
function App() {
const userProps = {
name: "John Doe",
age: 30,
email: "john@example.com"
};
return <MyComponent {...userProps} />;
}
function MyComponent({ name, age, email }) {
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
}
function MyComponent({ name, age, ...rest }) {
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
<p>Other props: {JSON.stringify(rest)}</p>
</div>
);
}
rest에는 email, address처럼 명시하지 않은 나머지 prop이 객체로 들어온다.
실무에서는 남용보다는 필요한 곳에서만 사용 권장.
function App() {
// 원본 user 객체
const user = { name: "John Doe", birthdate: "1990-01-01", age: 30 };
// 필요한 age 속성만 포함한 객체 생성
const ageOnlyUser = { age: user.age };
return <MyComponent user={ageOnlyUser} />;
}
function MyComponent({ user: { age } }) {
return (
<div>
<p>Age: {age}</p>
</div>
);
}
function App() {
const user = { name: "John Doe", birthdate: "1990-01-01", age: 30 };
return <MyComponent user={user} />;
}
function MyComponent({ user: { age } }) {
return (
<div>
<p>Age: {age}</p>
</div>
);
}
function MyComponent({ user: { age } })는 "user prop 안의 age만 변수로 꺼내 쓰겠다"는 뜻이다.
중첩 구조 분해는 객체가 항상 존재한다고 가정한다. 객체가 undefined일 경우 에러가 발생한다.
// ❌ user가 undefined면 에러 발생
function MyComponent({ user: { age } }) {
return <div>{age}</div>;
}
// ✅ 안전한 방법 1: 기본값 제공
function MyComponent({ user: { age } = {} }) {
return <div>{age}</div>;
}
// ✅ 안전한 방법 2: Optional Chaining 사용
function MyComponent({ user }) {
const age = user?.age;
return <div>{age}</div>;
}
// ✅ 안전한 방법 3: 기본값과 함께
function MyComponent({ user = {} }) {
const { age } = user;
return <div>{age}</div>;
}
children은 특별한 prop으로, 컴포넌트 태그 사이의 내용을 자동으로 전달받는다.
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
// 사용
function App() {
return (
<Card>
<h1>Title</h1>
<p>Content goes here</p>
</Card>
);
}
children을 활용하면 컴포넌트를 더 유연하고 재사용 가능하게 만들 수 있다.
이벤트 핸들러나 콜백 함수를 props로 전달할 수 있다. 이를 통해 자식 컴포넌트에서 부모 컴포넌트의 상태를 변경하거나 이벤트를 알릴 수 있다.
function Button({ onClick, label }) {
return <button onClick={onClick}>{label}</button>;
}
function App() {
const handleClick = () => {
alert("Button clicked!");
};
return <Button onClick={handleClick} label="Click Me" />;
}
function Counter({ count, onIncrement }) {
return (
<div>
<p>Count: {count}</p>
<button onClick={onIncrement}>Increment</button>
</div>
);
}
function App() {
const [count, setCount] = useState(0);
return (
<Counter
count={count}
onIncrement={() => setCount(count + 1)}
/>
);
}
일관된 네이밍은 코드 가독성을 높인다.
on으로 시작 (onClick, onChange, onSubmit)handle로 시작 (handleClick, handleChange, handleSubmit)function SearchBar({ onSearch }) {
const handleSubmit = (e) => {
e.preventDefault();
onSearch(e.target.value);
};
return <form onSubmit={handleSubmit}>...</form>;
}
is, has, should 등의 접두사 사용function Button({ isDisabled, hasIcon, shouldAutoFocus }) {
return (
<button
disabled={isDisabled}
autoFocus={shouldAutoFocus}
>
{hasIcon && <Icon />}
Click Me
</button>
);
}
import PropTypes from 'prop-types';
function MyComponent({ name, age, email }) {
return <div>{name}, {age}, {email}</div>;
}
MyComponent.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
email: PropTypes.string
};
MyComponent.defaultProps = {
age: 0,
email: 'N/A'
};
interface MyComponentProps {
name: string;
age?: number;
email?: string;
}
function MyComponent({ name, age = 0, email = 'N/A' }: MyComponentProps) {
return <div>{name}, {age}, {email}</div>;
}
실무에서는 TypeScript 사용이 강력히 권장된다. 컴파일 시점에 타입 오류를 잡을 수 있어 안정성이 높아진다.