Props

props는 상위 리액트 컴포넌트에서 전달받은 프로퍼티이다. 일반 자바스크립트 합수의 arguments와 유사하다고 생각하면 편하다. 단순히 값을 전달할 수도 있고, 아래와 같이 객체들로 이루어진 배열을 전달할 수도 있다.

const contacts = [
  {
    "id": "ryan",
    "name": "Ryan Florence",
    "email": "[email protected]",
    "avatarURL": "http://localhost:5001/ryan.jpg"
  },
  {
    "id": "michael",
    "name": "Michael Jackson",
    "email": "[email protected]",
    "avatarURL": "http://localhost:5001/michael.jpg"
  },
  {
    "id": "tyler",
    "name": "Tyler McGinnis",
    "email": "[email protected]",
    "avatarURL": "http://localhost:5001/tyler.jpg"
  }
]

ListContacts 의 프로퍼티로 contacts를 전달하면, ListContacts컴포넌트에서는 this.props.contacts로 배열에 접근할 수 있다.

//app.js
class App extends Component {
  render() {
    return (
      <div>
        {/* contacts배열을 contacts라는 이름의 프로퍼티로 넘겨준다.*/}
        <ListContacts contacts={contacts}/>

      </div>
    );
  }
}

export default App;

이때 ListContacts는 stateless 컴포넌트이다. 굳이 class로 컴포넌트를 만들 필요가 없기 때문에, Functional 컴포넌트로 변경해준다. 이때 함수는 인자로 props를 받고 컴포넌트내에서 this.props대신 props를 사용한다.

//ListContacts.js
import React , { Component } from 'react';


//functional컴포넌트로 변경 전 stateless 컴포넌트
class ListContacts extends Component{
  render(){
    //props를 사용하기 편리하도록 destructuring 해준다.
    const { contacts } = this.props;
    return(
        <ol className="contact-list">
          {contacts.map(contact => (
            <li key={contact.id} className="contact-list-item">
              <div className="contact-avatar" style={{
                backgroundImage: `url(${contact.avatarURL})`
              }}/>
              <div className="contact-details">
                <p>{contact.name}</p>
                <p>{contact.email}</p>
              </div>
              <button className="contact-remove">
                Remove
              </button>
            </li>
          ))}
        </ol>
    );
  }
}

//Functional 컴포넌트
//함수에 인자로 받은 props를 destructuring 해줬다.

function ListContacts({contacts}){
    return(
        <ol className="contact-list">
          {contacts.map(contact => (
            <li key={contact.id} className="contact-list-item">
              <div className="contact-avatar" style={{
                backgroundImage: `url(${contact.avatarURL})`
              }}/>
              <div className="contact-details">
                <p>{contact.name}</p>
                <p>{contact.email}</p>
              </div>
              <button className="contact-remove">
                Remove
              </button>
            </li>
          ))}
        </ol>
    );
}

export default ListContacts;

prop을 넘겨줄때 실수로 배열이 아닌 객체를 넘겨주면 어떻게 될까? 물론 에러가 뜨는데, 이것이 어떤이유로 발생했는지는 스스로 생각해야한다.

prop-Types로 전달받는 prop의 타입을 미리 지정해주면 에러가났을때 조금 힌트를 주면서, 앱의 안정성에도 도움이 되며, 여러명의 개발자가 코드를 볼때도 도움이 된다.

npm install --save prop-types
//ListContacts.js
import React from 'react';
import PropTypes from 'prop-types';

//생략
ListContacts.propTypes = {
  contacts: PropTypes.array.isRequired
}

State

props는 상위 컴포넌트에서 전달받은 읽기 전용(read-only) 데이터이다. 때문에 수정이 불가능하다.(immutable). state는 컴포넌트 내부에서 관리되는 데이터로서 수정이 가능하다.

위 예제 코드에서 contacts 배열은 App 컴포넌트와는 아무런 접점이 없다. 하지만 ListContacts컴포넌트와 직접적인 관계를 갖고있고 삭제버튼까지 만들어버렸기 때문에 언젠가 수정할 가능성이 농후하다. 하지만 props데이터이기 때문에 수정이 불가능하다. 그러한 이유로 contacts는 컴포넌트 내부에서 state로 관리해도 무방한(그러는게 더 좋은) 데이터이다. contacts를 ListContacts컴포넌트의 state에 넣어주었다.

class ListContacts extends Component{
  state = {
    contacts: [
      {
        "id": "ryan",
        "name": "Ryan Florence",
        "email": "[email protected]",
        "avatarURL": "http://localhost:5001/ryan.jpg"
      },
      {
        "id": "michael",
        "name": "Michael Jackson",
        "email": "[email protected]",
        "avatarURL": "http://localhost:5001/michael.jpg"
      },
      {
        "id": "tyler",
        "name": "Tyler McGinnis",
        "email": "[email protected]",
        "avatarURL": "http://localhost:5001/tyler.jpg"
      }
    ]
  }

  render(){
    //간단하게 props만 state로 바꿔주었다. destructuring을 왜사용해야 하는지 좋은예
    const { contacts } = this.state;
    return(
        {/*생략*/}
    );
  }
}

setState

state의 정보를 this.state.name = "something"와 같이 변경할 수 있을것 같지만 그럴수 없다. state를 변경할 수 있는 방법은 setState()하나뿐이다. setState()를 사용하면 컴포넌트는 새로운 state를 적용하기 위해 render()메소드를 콜한다. setState의 사용법은 functional, object 두가지이다.

//functional setState
//변경하고자하는 state가 변경전과 연관이 있을경우

this.setState((prevState) => ({
  count: preState.count + 1
}));

//object setState
//사실 별차이없다.

this.setState({
  count: this.state.count + 1
});

setState()는 주로 컴포넌트의 메소드들과 연결돼서 사용된다. 컴포넌트에 delete버튼으로 state를 변경해보자.

<button className="contact-remove" onClick={() => this.removeContact(contact.id)}>Remove</button>
removeContact = (id) => {
    console.log(id);

    //functional setState
    // this.setState((state) => ({
    //   contacts: state.contacts.filter((c) => c.id !== id)
    // }));

    this.setState({
      contacts: this.state.contacts.filter((c) => c.id !== id)
    });
  }

참고

컨트롤드 컴포넌트의 이점

  • instant input validation
  • conditionally disable/enable buttons
  • enforce input formats

results matching ""

    No results matching ""