props


React 에서의 데이터 흐름은 단방향으로 부모(상위) 컴포넌트에서 자식(하위) 컴포넌트로 전달되는 구조입니다.
이러한 흐름에서 부모 컴포넌트에서 자식 컴포넌트로 전달되는 데이터는 props 라는 객체로 전달되며 properties 의 줄임말 입니다.

그림 1 props 전달 예시

그림 1은 현재 자판기 예제의 데이터 전달 방식으로 VendingMachine(부모 컴포넌트)에서 자판기의 음료들에 대한 정보를 갖습니다.
VendingMachine은 음료들에 대한 정보를 각각 Drink(자식 컴포넌트)에 전달하여 음료들을 구성하게 됩니다.
이런 경우 VendingMachine에서 Array형태의 음료 정보들을 각각 Drinkprops를 통해 하나의 음료 정보를 전달합니다.

그림 1에서는 객체만 props로 전달 했지만 JavaScript에서는 함수도 객체이므로 함수도 전달이 가능하며 전달 받은 자식 컴포넌트는 props를 자신의 자식 컴포넌트로 전달할 수도 있습니다.

예제

자판기 예제를 통해서 Props의 전달을 알아보겠습니다.

자판기 예제 구조에서는 VendingMachine.js가 가장 상위의 부모 컴포넌트로 하위에는 판매 음료를 나타내는 Drink.js, 투입 금액을 나타내는 InputPanel.js, 선택한 음료를 받는 ExitShelf.js를 자식 컴포넌트로 갖습니다.

  • VendingMachine.js : 최상위 부모 컴포넌트
    • Drink.js : 판매 음료 컴포넌트
    • InputPanel.js : 투입 금액 컴포넌트
    • ExitShelf.js : 음료 퇴출구 컴포넌트

다음은 자판기의 컴포넌트 구조를 나타낸 그림입니다.

그림 2 자판기 컴포넌트 구조

현재 예제인 자판기에서의 구조는 각각의 음료에 해당하는 Drink.js 컴포넌트는 단순히 VendingMachine.js(부모 컴포넌트)로 부터 음료에 대한 정보(이름, 개수, 가격 등)를 props로 전달받아 음료에 대한 정보만 받아 표현하는 컴포넌트 입니다.
음료 정보에 대한 관리(개수 증감, 가격 설정 등)는 부모 컴포넌트인 VendingMachine.js가 담당합니다.

VendingMachineconstructor(생성자)에서 음료들에 대한 정보를 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에 속성명을 지정하여 { }에 값을 전달하면 됩니다.
예제에서는 판매 음료의 정보를 출력하는 컴포넌트인 Drinkitem이라는 속성명으로 음료에 대한 정보를 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]}/>
        ...
    )
  }
}

VendingMachinestate에 저장된 음료들의 정보를 모두 출력하기 위해 반복문을 통해 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에서 Drinkprops을 전달하기만 하였습니다.
지금 부터는 전달 받은 props을 자식컴포넌트에서 사용하는 방법에 대해 알아 보겠습니다.
Drink 컴포넌트는 클래스 컴포넌트로서 전달 받은 propsthis.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라는 객체를 추가하였습니다. 그렇지 않은 경우 Drinkprops로 전달되는 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)
}
그림 3 prop-types Waring Console

불변성(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)한 객체로 직접 수정하지 못합니다.

React + MobX
SPA 라이브러리인 React를 MobX state 관리 라이브러리와 함께 배워봅시다.