다섯 단계로 Clock
과 같은 함수 컴포넌트를 클래스로 변환할 수 있습니다.
React.Component
를 확장하는 동일한 이름의 ES6 class를 생성합니다.render()
라고 불리는 빈 메서드를 추가합니다.render()
메서드 안으로 옮깁니다.render()
내용 안에 있는 props
를 this.props
로 변경합니다.많은 컴포넌트가 있는 애플리케이션에서 컴포넌트가 삭제될 때 해당 컴포넌트가 사용 중이던 리소스를 확보하는 것이 중요합니다.
Clock
이 처음 DOM에 렌더링 될 때마다 타이머를 설정하려고 합니다. 이것은 React에서 “마운팅”이라고 합니다.
또한 Clock
에 의해 생성된 DOM이 삭제될 때마다 타이머를 해제하려고 합니다. 이것은 React에서 “언마운팅”이라고 합니다.
컴포넌트 클래스에서 특별한 메서드를 선언하여 컴포넌트가 마운트되거나 언마운트 될 때 일부 코드를 작동할 수 있습니다.
주목해야 될점은 DOM에 컴포넌트가 탑재될때 발생하는 마운팅, DOM에 컴포넌트가 삭제될때 발생하는 언마운팅.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
이러한 메서드들은 “생명주기 메서드”라고 불립니다.
componentDidMount()
메서드는 컴포넌트 출력물이 DOM에 렌더링 된 후에 실행됩니다. 이 장소가 타이머를 설정하기에 좋은 장소입니다.
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
this
(this.timerID
)에서 어떻게 타이머 ID를 제대로 저장하는지 주의해주세요.
this.props
가 React에 의해 스스로 설정되고 this.state
가 특수한 의미가 있지만, 타이머 ID와 같이 데이터 흐름 안에 포함되지 않는 어떤 항목을 보관할 필요가 있다면 자유롭게 클래스에 수동으로 부가적인 필드를 추가해도 됩니다.
componentWillUnmount()
생명주기 메서드 안에 있는 타이머를 분해해 보겠습니다.
componentWillUnmount() {
clearInterval(this.timerID);
}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
이제 시계는 매초 째깍거립니다.
현재 어떤 상황이고 메서드가 어떻게 호출되는지 순서대로 빠르게 요약해 보겠습니다.
<Clock />
가 ReactDOM.render()
로 전달되었을 때 React는 Clock
컴포넌트의 constructor를 호출합니다. Clock
이 현재 시각을 표시해야 하기 때문에 현재 시각이 포함된 객체로 this.state
를 초기화합니다. 나중에 이 state를 업데이트할 것입니다.Clock
컴포넌트의 render()
메서드를 호출합니다. 이를 통해 React는 화면에 표시되어야 할 내용을 알게 됩니다. 그 다음 React는 Clock
의 렌더링 출력값을 일치시키기 위해 DOM을 업데이트합니다.Clock
출력값이 DOM에 삽입되면, React는 componentDidMount()
생명주기 메서드를 호출합니다. 그 안에서 Clock
컴포넌트는 매초 컴포넌트의 tick()
메서드를 호출하기 위한 타이머를 설정하도록 브라우저에 요청합니다.tick()
메서드를 호출합니다. 그 안에서 Clock
컴포넌트는 setState()
에 현재 시각을 포함하는 객체를 호출하면서 UI 업데이트를 진행합니다. setState()
호출 덕분에 React는 state가 변경된 것을 인지하고 화면에 표시될 내용을 알아내기 위해 render()
메서드를 다시 호출합니다. 이 때 render()
메서드 안의 this.state.date
가 달라지고 렌더링 출력값은 업데이트된 시각을 포함합니다. React는 이에 따라 DOM을 업데이트합니다.Clock
컴포넌트가 DOM으로부터 한 번이라도 삭제된 적이 있다면 React는 타이머를 멈추기 위해 componentWillUnmount()
생명주기 메서드를 호출합니다.setState()
에 대해서 알아야 할 세 가지가 있습니다.
예를 들어, 이 코드는 컴포넌트를 다시 렌더링하지 않습니다.
// Wrong
this.state.comment = 'Hello';
대신에 setState()
를 사용합니다.
// Correct
this.setState({comment: 'Hello'});
this.state
를 지정할 수 있는 유일한 공간은 바로 constructor입니다.
React는 성능을 위해 여러 setState()
호출을 단일 업데이트로 한꺼번에 처리할 수 있습니다.
this.props
와 this.state
가 비동기적으로 업데이트될 수 있기 때문에 다음 state를 계산할 때 해당 값에 의존해서는 안 됩니다.
예를 들어, 다음 코드는 카운터 업데이트에 실패할 수 있습니다.
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
이를 수정하기 위해 객체보다는 함수를 인자로 사용하는 다른 형태의 setState()
를 사용합니다. 그 함수는 이전 state를 첫 번째 인자로 받아들일 것이고, 업데이트가 적용된 시점의 props를 두 번째 인자로 받아들일 것입니다.
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
위에서는 화살표 함수를 사용했지만, 일반적인 함수에서도 정상적으로 작동합니다.
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
setState()
를 호출할 때 React는 제공한 객체를 현재 state로 병합합니다.
예를 들어, state는 다양한 독립적인 변수를 포함할 수 있습니다.
constructor(props) {
super(props);
this.state = {
posts: [], comments: [] };
}
별도의 setState()
호출로 이러한 변수를 독립적으로 업데이트할 수 있습니다.
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts });
});
fetchComments().then(response => {
this.setState({
comments: response.comments });
});
}
병합은 얕게 이루어지기 때문에 this.setState({comments})
는 this.state.posts
에 영향을 주진 않지만 this.state.comments
는 완전히 대체됩니다.
부모 컴포넌트나 자식 컴포넌트 모두 특정 컴포넌트가 유상태인지 또는 무상태인지 알 수 없고, 그들이 함수나 클래스로 정의되었는지에 대해서 관심을 가질 필요가 없습니다.
이 때문에 state는 종종 로컬 또는 캡슐화라고 불립니다. state가 소유하고 설정한 컴포넌트 이외에는 어떠한 컴포넌트에도 접근할 수 없습니다.
컴포넌트는 자신의 state를 자식 컴포넌트에 props로 전달할 수 있습니다.
<FormattedDate date={this.state.date} />
FormattedDate
컴포넌트는 date
를 자신의 props로 받을 것이고 이것이 Clock
의 state로부터 왔는지, Clock
의 props에서 왔는지, 수동으로 입력한 것인지 알지 못합니다.
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}