React 컴포넌트는 서로 데이터를 주고받을 때 props라는 것을 사용한다. 부모 컴포넌트는 자식 컴포넌트에게 props를 넘겨줌으로써 전달할 수 있다. props는 HTML의 속성과 비슷하다고 생각될 수 있지만, props로는 객체나 배열, 함수 같은 모든 JavaScript에 존재하는 값을 넘길 수 있다.
익숙한 props
props는 JSX 태그에 넘겨주는 정보를 말한다. 예를 들어, className
, src
, alt
, width
나 height
는, <img>
에 넘겨줄 수 있는 props의 예시이다.
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>
에 넘겨줄 수 있는 props의 종류는 미리 정해져있다. (ReactDOM은 HTML 표준에 준거한다.) 또한 <Avatar>
와 같은 직접 작성한 컴포넌트의 경우, 임의의 props를 넘겨주고 그것을 커스터마이징하고 있다. 아래에서 사용하는 방법을 설명한다.
컴포넌트에 props를 넘긴다
아래 코드에선, Profile
컴포넌트는 자식 컴포넌트인 <Avatar>
에 어떤 props도 넘겨줄 수 없다.
export default function Profile() {
return (
<Avatar />
);
}
아래의 두 가지 단계를 거쳐, Avatar
에 props를 줄 수 있다.
Step 1: 자식 컴포넌트에 props를 넘긴다
우선, Avatar
에 무언가 props를 넘긴다. 예를 들어, person(객체)와 size(값)을 넘겨보자.
export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}
혹시 person=
뒤에 있는 이중 중괄호를 잘 알지 못한다면, 이것이 JSX 중괄호 내에 있는 단순한 객체라는 것을 생각하자.
그렇게 props를 Avatar
컴포넌트 내에서부터 읽어낼 수 있다.
Step 2: 자식 컴포넌트에서 props를 읽어낸다
이런 props를 읽어내려면, function Avatar
바로 뒤에 ({
과 {)
안에, 콤마(,)로 끊어서 person, size
와 같이 이름을 지정한다. 이것으로 Avatar의 코드 내에 변수와 같이 props를 사용할 수 있게 된다.
function Avatar({ person, size }) {
// person과 size는 여기에서 사용 가능하다.
}
Avatar
내에, person
이나 size
를 사용해 무언가 렌더링하는 로직을 추가로 작성하면 완성이다.
이것으로 Avatar
에 여러가지 props를 넘김으로서 여러가지 표시되는 것들을 바꿀 수 있다. 실제로 예시를 만져보자.
// App.js
import { getImageUrl } from './utils.js';
function Avatar({ person, size }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
);
}
export default function Profile() {
return (
<div>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
<Avatar
size={80}
person={{
name: 'Aklilu Lemma',
imageId: 'OKS67lh'
}}
/>
<Avatar
size={50}
person={{
name: 'Lin Lanying',
imageId: '1bX5QH6'
}}
/>
</div>
);
}
// utils.js
export function getImageUrl(person, size = 's') {
return (
'https://i.imgur.com/' +
person.imageId +
size +
'.jpg'
);
}
props 덕분에, 부모와 자식 컴포넌트를 독립해서 생각할 수 있다. 예를 들어, Profile
로 person
이나 size
를 변경할 때 Avatar
내에서 이 props가 어떻게 사용될지 고려하지 않아도 된다. 같은 말로, Avatar가 이런 props를 어떻게 사용할 지는, Profile
을 고려하지 않고 변경할 수 있다.
props란 스스로 조정할 수 있는 컨트롤러인 '핸들러'와 같은 것이다. 함수에 관한 인수와 같은 역할을 하고 있다. 오히려, props가 당신의 컴포넌트의 유일한 인수다. React 컴포넌트는 props
라고 하는 객체를 유일한 인수로 받아들이고 있다.
function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}
보통은 props
객체 전체를 필요로 하는 경우는 없기 때문에, 각각의 props에 분할하여 대입한다.
props를 선언할 때는(
와)
사이에,{
과}
라는 중괄호의 쌍을 쓰는 걸 잊지않도록 한다.
function Avatar({ person, size }) { // ... }
이 구문은 분할대입(destructuring)이라고 불리기 때문에, 함수의 인수에서부터 속성을 읽어내는 코드와 같다.
function Avatar(props) { let person = props.person; let size = props.size; // ... }
props의 기본값을 지정하기
props에, 값이 넘겨지지 않았을 경우 폴백으로 사용될 기본값을 지정할 경우, 분할대입한 객체의 파라미터 명 뒤에 =
와 기본값을 작성할 수 있다.
function Avatar({ person, size = 100 }) {
// ...
}
이걸로, size
프로퍼티를 지정하지 않고 <Avatar person={...} />
와 같이 렌더링할 경우, size
는 100
으로 설정된다.
이 기본값은 size
가 아닐 경우나 size={undefined}
로 넘겨진 경우에 사용된다. size={null}
이나 size={0}
으로 넘겨진 경우엔 기본값이 쓰이지 않는다.
JSX 스프레드 구문으로 props를 전송한다
가끔, props를 받는 것이 계속해서 길어지는 경우가 있다.
function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);
}
반복되는 긴 코드가 나쁜 건 아니고, 그 방법이 읽기 쉬운 경우도 있다. 그러나 간결하게 작성하는 것이 더 가치있는 경우도 있다. 이 Profile
이 Avatar
에 대응하고 있기 때문에, 컴포넌트 내에 props를 그대로 자식 컴포넌트에 전송하는 것이 가능하다. Profile
은 props를 직접적으로 사용하는 건 아니기 때문에, 아래와 같은 '스프레드' 구문을 사용해 짧게 작성하는 경우가 이득일 수 있다.
function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}
이것에 의해, Profile
에 넘겨진 props를, 각각의 이름으로 열거하기만 함으로써 모든 값을 Avatar
에 전송할 수 있다.
스프레드 구문은 신중하게 사용해야 한다. 이 구문을 거의 모든 컴포넌트에서 사용한다면, 무언가가 잘못될 수 있다. 많을 경우, 컴포넌트를 분할해 JSX로써 children을 넘겨야한다는 신호일지도 모른다. 지금부터 그것에 대해 얘기해보겠다.
children으로써 JSX를 넘긴다
브라우저 내장 태그를 네스팅하는 경우는 자주 있다.
<div>
<img />
</div>
위와 동일하게, 독자적인 컴포넌트도 네스팅하고 싶은 경우가 있다.
<Card>
<Avatar />
</Card>
이와 같이 JSX 태그 내에서 컨텐츠를 네스팅할 경우, 부모 컴포넌트는 그 안에 children
이라는 props로써 받아들인다. 예를 들어, 아래의 Card 컴포넌트는, <Avatar />
가 설정된 children
프로퍼티를 받아서, 래퍼 div 요소의 내부에 그것을 렌더링한다.
// App.js
import Avatar from './Avatar.js';
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</Card>
);
}
// Avatar.js
import { getImageUrl } from './utils.js';
export default function Avatar({ person, size }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
);
}
// utils.js
export function getImageUrl(person, size = 's') {
return (
'https://i.imgur.com/' +
person.imageId +
size +
'.jpg'
);
}
<Card>
내의 <Avatar>
를 어떤 텍스트로 교체해보면, 네스팅되어있는 어떤 콘텐츠라도 Card
컴포넌트는 그것을 둘러싸서 표시할 수 있다는 것을 확인해보자. 안에 무언가 표시될지 사전에 알아갈 필요는 없다. 이런 유연한 패턴은 여러 곳에서 발견할 수 있다.
children
프로퍼티를 소유한 컴포넌트엔, 부모가 임의의 JSX을 메우기 위한 빈 공간을 만든다고 생각할 수도 있다. children
은, 패널이나 그리드와 같은 시각적인 무언가를 둘러싼 표시로써 사용할 수 있다.
Props는 시간과 함께 변화한다
아래의 Clock
컴포넌트는 부모 컴포넌트에서 color
와 time
이라는 두 가지 props를 받는다. (부모 컴포넌트의 코드는, state를 사용하고 있기 때문에 생략되어있다.)
아래의 선택 박스에서 색상을 바꿔보자.
export default function Clock({ color, time }) {
return (
<h1 style={{ color: color }}>
{time}
</h1>
);
}
이 예시는, 컴포넌트는 시간의 변화에 따라 다른 props를 받을 가능성이 있다는 걸 알려준다. props는 보통 고정적이라고 생각할 수 있는데, 편견이다. 여기선 time
프로퍼티는 매 초마다 변화하고, color
는 당신이 예시로 주어진 색깔을 선택할 때마다 변화한다. props란 컴포넌트가 처음으로 만들어지는 시점 뿐만 아니라, 임의의 시점에 컴포넌트의 데이터를 반환하는 것이다.
그러나, props는 불변(immutable)한다. 이것은 '변하지 않는'다는 의미의 CS 용어다. 컴포넌트의 props가 (예를 들어 사용자가 조작하는 경우나 새로운 데이터가 왔을 때에 대한) 변하지 않으면 안되는 상황, 부모 컴포넌트에 예시의 props, 즉 새로운 객체를 넘겨줄 필요가 있다. 오래된 props는 잊고, 사용하던 메모리는 JavaScript 엔진이 회수한다. (가비지 콜렉터)
props를 새로 작성하려고 하면 안된다. (위의 색상 선택과 같이) 사용자의 입력에 대응할 필요가 있을 땐, 'state의 집합'이 필요하다.
정리
- props를 넘기기 위해선 HTML로 속성을 작성하는 것과 같은 방법으로 JSX내에 작성한다.
- props를 읽어내려면,
function Avatar({ person, size })
와 같은 구조 분해 할당문을 사용한다. size = 100
과 같이 기본값을 지정할 수 있으며, 이것은 props가 없거나 undefined일 경우 사용된다.<Avatar {...props} />
와 같은 JSX 스프레드 구문으로 모든 props를 전송할 수 있지만 남발하면 안된다.<Card><Avatar /></Card>
와 같이 네스팅된 JSX를 작성하면 Avatar가Card
컴포넌트의children
프로퍼티가 된다.- props는 어떤 시점에서 읽어내기 위한 스냅 샷이다. 렌더링할 때마다 새로운 버전의 props를 받는다.
- props를 다시 작성하는 것은 안된다. 상호 작용성이 필요한 경우 state를 설정할 필요가 있다.