props
React
에서의 데이터 흐름은 단방향으로 부모(상위) 컴포넌트에서 자식(하위) 컴포넌트로 전달되는 구조입니다.
이러한 흐름에서 부모 컴포넌트에서 자식 컴포넌트로 전달되는 데이터는 props
라는 객체로 전달되며 properties
의 줄임말 입니다.
그림 1은 현재 자판기 예제의 데이터 전달 방식으로 VendingMachine(부모 컴포넌트)에서 자판기의 음료들에 대한 정보를 갖습니다.
VendingMachine은 음료들에 대한 정보를 각각 Drink(자식 컴포넌트)에 전달하여 음료들을 구성하게 됩니다.
이런 경우 VendingMachine
에서 Array형태의 음료 정보들을 각각 Drink
로 props
를 통해 하나의 음료 정보를 전달합니다.
그림 1에서는 객체만 props
로 전달 했지만 JavaScript
에서는 함수도 객체이므로 함수도 전달이 가능하며 전달 받은 자식 컴포넌트는 props
를 자신의 자식 컴포넌트로 전달할 수도 있습니다.
예제
자판기 예제를 통해서 Props
의 전달을 알아보겠습니다.
자판기 예제 구조에서는 VendingMachine.js
가 가장 상위의 부모 컴포넌트로 하위에는 판매 음료를 나타내는 Drink.js
, 투입 금액을 나타내는 InputPanel.js
, 선택한 음료를 받는 ExitShelf.js
를 자식 컴포넌트로 갖습니다.
VendingMachine.js
: 최상위 부모 컴포넌트Drink.js
: 판매 음료 컴포넌트InputPanel.js
: 투입 금액 컴포넌트ExitShelf.js
: 음료 퇴출구 컴포넌트
다음은 자판기의 컴포넌트 구조를 나타낸 그림입니다.
현재 예제인 자판기에서의 구조는 각각의 음료에 해당하는 Drink.js
컴포넌트는 단순히 VendingMachine.js
(부모 컴포넌트)로 부터 음료에 대한 정보(이름, 개수, 가격 등)를 props
로 전달받아 음료에 대한 정보만 받아 표현하는 컴포넌트 입니다.
음료 정보에 대한 관리(개수 증감, 가격 설정 등)는 부모 컴포넌트인 VendingMachine.js
가 담당합니다.
VendingMachine
의 constructor
(생성자)에서 음료들에 대한 정보를 state
에 저장하였습니다.
class VendingMachine extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{
name: 'coke',
price: 1000,
quantity: 30
},
{
name: 'cider',
price: 1100,
quantity: 20
},
{
name: 'water',
price: 900,
quantity: 40
},
{
name: 'coffee',
price: 1000,
quantity: 20
},
...
]
}
}
render(){
...
}
}
props 전달 방법
props
를 전달 하는 방법은 부모 컴포넌트에서 자식 컴포넌트의 JSX에 속성명을 지정하여 { }
에 값을 전달하면 됩니다.
예제에서는 판매 음료의 정보를 출력하는 컴포넌트인 Drink
에 item
이라는 속성명으로 음료에 대한 정보를 props
로 전달하였습니다.
class VendingMachine extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{
name: 'coke',
price: 1000,
quantity: 30
},
...
]
}
}
render() {
const { items } = this.state;
return (
...
<Drink item={items[0]}/>
...
)
}
}
VendingMachine
의 state
에 저장된 음료들의 정보를 모두 출력하기 위해 반복문을 통해 Drink
컴포넌트로 각각의 판매 음료 정보를 전달합니다.
class VendingMachine extends React.Component {
constructor(props) {
...
}
render() {
const { items } = this.state;
const layout = []
for(let i =0 ; i<items.length; i){
layout.push(
<SegmentGroup horizontal>
<Drink item={items[i++]}/>
<Drink item={items[i++]}/>
<Drink item={items[i++]}/>
<Drink item={items[i++]}/>
</SegmentGroup>
)
}
return(
...
{
layout
}
...
)
}
}
props 사용
이전 까지는 VendingMachine
에서 Drink
로 props
을 전달하기만 하였습니다.
지금 부터는 전달 받은 props
을 자식컴포넌트에서 사용하는 방법에 대해 알아 보겠습니다.
Drink
컴포넌트는 클래스 컴포넌트
로서 전달 받은 props
는 this.props
객체에 부모에서 지정한 속성명으로 전달됩니다.
class Drink extends React.Component {
render() {
const {item} = this.props; // this.props객체에 속명으로 전달
return (
...
);
}
}
함수형 컴포넌트
에서는 파라미터로 props
라는 객체가 전달되며 클래스 컴포넌트
와 동일하게 부모에서 지정한 속성명으로 전달 됩니다.
// arrow function
const Drink = ({item}) =>{ // 파라미터에 props객체가 전달
return(
...
)
}
// function
const Drink = function(props){
const {item} = props;
return(
...
)
}
defaultProps
이전까지의 예제에서는 음료의 개수를 맞추기위해 마지막에 empty
라는 객체를 추가하였습니다. 그렇지 않은 경우 Drink
의 props
로 전달되는 item
은 null
이 전달 됩니다.
null
을 전달 받게 된다면 item
객체의 name
, price
, quantity
등의 속성이 존재하지 않기 때문에 오류가 발생하게 됩니다.
null
을 방지하기 위해 defaultProps를 사용하여 기본 props
를 설정할 수 있습니다.
// 클래스 컴포넌트
class Drink extends React.Component {
static defaultProps = {
item: {
name: 'empty',
price: 0,
quantity: 0
}
}
render() {
...
}
}
//함수형 컴포넌트
const Drink = ({ item }) => {
return (
...
)
}
Drink.defaultProps = {
item: {
name: 'empty',
price: 0,
quantity: 0
}
}
propTypes
javascript
는 타입을 정하지 않고 자유롭게 타입을 사용하며 React에서도 마찬가지로 자유롭습니다. 하지만 이러한 자유로움으로 인해 버그 발생과 디버깅이 어려워질 수 있습니다. 이를 예방하기 위해 prop-types
라이브러리를 이용하여 필요한 속성과 어떤 타입의 데이터인지 지정할 수 있습니다.
지정한 속성과 타입과 일치하지 않은 데이터가 전달 되며 콘솔에 해당 정보를 출력합니다.
// 클래스 컴포넌트와 함수형 컴포넌트의 설정이 동일합니다.
import PropTypes from 'prop-types';
class Drink extends React.Component {
...
}
const itemShape = {
name : PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
quantity: PropTypes.number.isRequired
}
Drink.propTypes ={
item:PropTypes.shape(itemShape)
}
불변성(ReadOnly)
All React components must act like pure functions with respect to their props.
React 홈페이지에서 props
의 특징인 불편성에 대해 설명하는 문장으로 props
는 순수함수 처럼 동작해야 한다고 합니다.
즉 컴포넌트 내부에서 전달받은 props
의 값을 변경해서는 않된다는 것입니다.
예제를 통해 예를 들면 Get Button을 통해 음료를 선택할 때 음료의 수량(Quantity)를 감소 시키기 위해서 Drink
컴포넌트에서 직접 전달 받은 props
을 수정하는 것이 아닌 VendingMachine
컴포넌트의 state
에서 감소된 props
을 전달 받습니다.
// 잘못된 방법
class Drink extends React.Component {
onClickGetButton = () =>{
const {item} = this.props;
item.quantity = item.quantity -1;
}
render(){
return(
...
<Button size='mini' color='red' onClick={()=>this.onClickGetButton()}>GET</Button>
...
)
}
}
class VendingMachine extends React.Component {
...
onClickGetButton = (i) => {
this.setState({
items: this.state.items.map((item,index) => {
if (i === index&&item.quantity>0) {
return { ...item, quantity: --item.quantity }
}
return item;
})
})
}
...
render(){
...
<Drink item={items[i]} onClickGetButton={this.onClickGetButton} index={i++}/>
...
}
}
/////////////////////////////
class Drink extends React.Component {
...
render(){
const { item, onClickGetButton, index } = this.props;
return(
...
<Button size='mini' color='red' onClick={() => onClickGetButton(index)}>GET</Button>
...
)
}
}
마치며
지금까지 props
에 관하여 알아보았습니다.
props
에 대해 요약한다면 React
는 부모에서 자식으로 데이터가 단반향으로 전달되며 이렇게 전달되는 데이터는 props
라는 객체에 담겨서 자식으로 전달되며 defaultProps
을 통해 기본값을 설정할 수 있고 prop-types
라이브러리를 사용하여 props
를 검사할 수 있으며 불변(ReadOnly)
한 객체로 직접 수정하지 못합니다.