React 컴포넌트는 상태 값을 이용해 UI를 표현한다.
컴포넌트 상태는 객체의 인스턴스 속성(Properties)을 이용해 관리하며, 컴포넌트간에 단방향으로 데이터를 주고받는 props
와 컴포넌트 내부에서 사용하는 state
가 있다.
props
는 컴포넌트에서 사용할 데이터 중 변경되지 않는 데이터를 다룰때 사용한다. 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 사용된다. props
는 변경이 불가능하다.
class Parent extends React.Component {
render() {
let name = "mike";
return (
<h3> Hello <Child name={name} /></h3>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log(this.props); // Object {name: "mike"}
return <a>{this.props.name}</a>;
}
}
// default Props를 사용할 수 있다.
Child.defaultProps = {
name: "unknown"
};
컴포넌트에서 관리하는 상태 값으로 유동적인 데이터를 다룰 때, state
를 사용한다. state
는 변경이 가능하고 변경할 때는 setState
메서드를 사용해 상태를 변경한다. setState
는 비동기로 동작하며 동작완료에 대한 콜백을 설정할 수 있다.
class Button extends React.Component {
constructor() {
super();
this.state = {
count: 0
};
}
updateCount = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<button onClick={this.updateCount}>
Clicked {this.state.count} times
</button>
);
}
}
상태 저장 여부의 관점에서 상태저장 컴포넌트와 상태비저장 컴포넌트로 나눌수 있다.
상태 저장 컴포넌트는 상태 객체와 라이프사이클을 가지는 컴포넌트다. 일반적으로 컴포넌트에 내부 state
를 가지는 컴포넌트를 말한다.
class ButtonCounter extends React.Component {
constructor() {
super();
this.state = { clicks: 0 };
}
handleClick = (e) => {
this.setState({ clicks: this.state.clicks + 1});
}
render() {
return (
<Button onClick={this.handleClick} text={`You've clicked me ${this.state.clicks} times!`} />
)
}
}
상태 비저장 컴포넌트는 상채 객체가 없으며 컴포넌트 메서드 또는 다른 React의 라이프사이클 이벤트 또는 메서드를 갖지 않는다.
일반적으로 컴포넌트에 내부 state
가 없으며, 함수로 구현되는 컴포넌트를 말한다.
const Button = props => (<button onClick={props.onClick}>{props.text}</button>)
상태 비저장 컴포넌트를 사용하면 컴포넌트를 간결하게 작성할 수 있으며, 개별 상태가 없으므로 이해하기 쉽고 예측이 용이하여 테스트를 간단히 할 수 있다. 또한 라이프사이클을 사용하지 않으므로 불필요한 검사 및 메모리 할당을 줄일 수 있다. React의 바람직한 사례는 상태비저장 컴포넌트를 많이 사용하고 상태저장 컴포넌트는 적게 사용하는것이다.
이 패턴은 댄 아브라모프(Dan Abramov)가 Smart & Dumb 컴포넌트란 이름으로 사용하였다. 후에 더욱 정확한 의미 전달을 위해 명칭이 변경되었다. 컨테이너 컴포넌트와 프레젠테이션 컴포넌트는 로직 분리의 관점에서 구분하는 표현이다.
단순히 props
, state
로 화면을 그리는데 넘겨받는 것이 아니라 Ajax 요청이나 localStorage 등을 통해 데이터를 fetching 해야 할 경우 데이터까지 다루게 되므로 재사용성이 떨어진다. 이러한 상황에서 동작을 다루는 부분과 표현을 다루는 부분을 분리하여 작성하는 컴포넌트 패턴이다. state
존재 여부가 두 컴포넌트를 구분하는 것은 아니다
class UserListContainer extends React.Component {
constructor() {
super();
this.state = { users: [] }
}
componentDidMount() {
fetchUsers(users => this.setState({ users }));
}
redner() {
return <UserList users={this.state.users} />
}
}
const UserList = props =>
(<ul>
{props.users.map(u => (
<li>{u.name} - {u.age} years old</li>
))}
</ul>)
React에는 PureComponent
가 존재하는데, Component
와 유사하지만 라이프사이클의 shouldComponentUpdate
에 얕은 비교(shallow comparison)를 하는 코드가 구현된 컴포넌트를 PureComponent
라고 한다. React의 렌더링에서 shouldComponentUpdate
가 true
를 반환하면 재랜더링을 하는 구조인데, props
와 state
가 기존과 동일한 경우 불필요한 상황에서 재렌더링을 방지하여 성능을 향상시키려는 목적에서 만들어졌다.
PureComponent
는 성능을 최적화하는데 활용되므로 성능상의 이슈에 맞닥뜨리지 않는 한 이 컴포넌트를 사용해야 하는지 고려할 필요는 없다.
class Example extends PureComponent {
// This won't re-render when the props DONT change
render() {
// ... do expensive compute on props ...
return <SomeComponent someProp={props.someProp}/>
}
}
if (type.prototype && type.prototype.isPureReactComponent) {
shouldUpdate = !shallowEqual(oldProps, props) || !shallowEqual(oldState, state);
}