State and Lifecycle
이전 섹션 중 하나였던 시계 예제를 되돌아보자.
지금까지 우리는 UI를 업데이트 시키는 단지 한가지 방법만을 배웠다.
렌더링된 출력물을 바꾸기 위해 우리는 ReactDOM.render()
를 호출했다.
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
이번 섹션에서는 Clock
컴포넌트를 진짜로 재활용 가능하고 캡슐화된 상태로 만드는 방법을 배울 것이다.
이 새로운 컴포넌트는 자체 타이머를 가질 것이고 매초마다 스스로 업데이트 할 것이다.
아래 코드를 캡슐화 시키는 것으로부터 시작하자.
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
하지만 위의 코드는 중요한 요건이 빠져있다. 타이머를 설정하고 매초마다 UI를 업데이트 하는 것은 Clock
내에
상세하게 구현되어야 한다는 것이다.
이상적인 형태로 우리는 시계 코드를 한번만 작성해 놓고 시계 스스로가 업데이트 할 수 있도록 할 것이다.
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
이렇게 구현하기 위해서는 Clock
컴포넌트 안에 “state”를 추가해야 한다.
State는 props와 유사하지만 private 속성을 가지고 있고 전적으로 컴포넌트에 의해 관리된다.
이전 내용에서 class로 선언된 컴포넌트는 추가적인 기능들이 있다고 했는데 Local state가 바로 그 것이고
이 Local state는 class들 안에서만 사용할 수 있다.
Converting a Function to a Class
다음의 5단계를 통해 functional로 만들어진 Clock
컴포넌트를 class로 만들 수 있다.
- functional 컴포넌트와 동일한 이름으로
React.Component
를 상속받는 ES6 클래스를 생성한다. render()
라는 비어있는 메소드 하나를 추가한다.- functional 컴포넌트의 함수 본체를
render()
메소드 안으로 옮긴다. render()
메소드 내에 있는props
를this.props
로 수정한다.- 비어있는 functional 컴포넌트를 삭제한다.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
이제 Clock
는 funtional이 아닌 class로 선언되었다.
Class로 선언되었기 때문에 local state나 lifecycle hooks같은 기능들을 사용할 수 있다.
Adding Local State to a Class
3단계를 거쳐 date
객체를 props로부터 state로 옮길 것이다.
1) render()
메소드 안에 있는 this.props.date
를 this.state.date
로 바꾼다.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
2) this.state
에 초기값을 할당하는 클래스 생성자를 추가한다.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
기본 생성자에게 props
를 전달하는 방법에 주의하라.
constructor(props) {
super(props);
this.state = {date: new Date()};
}
Class 컴포넌트에서는 기본 생성자를 호출할 때 반드시 props
를 파라미터로 넘겨야 한다.
3) <Clock />
element에서 date
prop을 삭제한다.
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
마침내 timer 코드를 시계 컴포넌트 안에 추가하였다.
결과 코드는 다음과 같다.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {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
컴포넌트가 자신의 timer를 설정하고 매초마다 스스로 업데이트되도록 만들 것이다.
Adding Lifecycle Methods to a Class
많은 컴포넌트를 가진 애플리케이션에서 컴포넌트가 사용한 리소스를 컴포넌트가 사용 종료될 때 메모리에서
해제 시키는 것은 매우 중요하다.
우리는 Clock
컴포넌트가 처음 DOM 내에 렌더링 되는 시점에만 timer를 설정하려고 한다. React에서는
이 과정을 “mounting”이라고 한다.
한편 DOM에서 생성된 컴포넌트가 제거될 때마다 timer를 지우려고 한다. React에서는 이 것을 “unmounting”
이라고 한다.
컴포넌트 내에는 컴포넌트가 mount 되거나 unmount 될 때마다 어떤 코드를 실행하는 특별한 메소드를 선언할
수 있다.
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>
);
}
}
이런 메소드들을 “lifecycle hooks”라고 부른다.componentDidMount()
hook는 컴포넌트의 출력이 DOM에 렌더링 된 후 실행된다. 그래서 이 메소드는
timer를 설정하기에 좋은 위치이다.
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
this
에 timer ID를 어떻게 저장하는지에 유의하라.
this.props
는 React 자체에 의해 설정되고 this.state
는 특별한 의미가 있지만 만일 시각적인 출력에
사용되지 않는 어떤 정보를 저장할 필요가 있는 경우에는 수작업을 통해 자유롭게 필드를 추가할 수 있다.
render()
안에서 사용되지 않는 것들은 state가 되어서는 안된다.
componentWillUnmount()
lifecycle hook에서는 timer를 지울 것이다.
componentWillUnmount() {
clearInterval(this.timerID);
}
마침내 매초마다 실행되는 tick()
메소드를 구현할 차례이다.
컴포넌트의 local state를 주기적으로 업데이트하기 위해 this.setState()
를 사용할 것이다.
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은 매초 작동할 것이다.
무슨일이 일어나고 있는지, 그리고 메소드가 호출되는 순간 어떤 순서로 진행되는지 간단하게 살펴보자
React.render()
로<Clock />
이 전달되면 React는Clock
컴포넌트의 생성자를 호출한다.Clock
은 현재 시간을 표시해야 하기 때문에 현재 시간을 가진 객체로this.state
를 초기화 한다. 뒤에 이 state를 업데이트한다.- 그리고 나서 React는
Clock
컴포넌트의render()
메소드를 호출한다. 이러한 방식으로 React는 화면에 무엇을 표시해야 하는지 알게 된다. 이후 React는Clock
의 렌더링 출력에 맞게 DOM을 업데이트한다. Clock
의 출력이 DOM에 삽입되면, React는componentDidMount()
lifecycle hook를 호출한다. 그 안에서Clock
컴포넌트는 브라우저에게 매초마다tick()
을 호출하는 timer를 설정하도록 요청한다.- 매초마다 브라우저는
tick()
메소드를 호출한다. 그 안에서Clock
컴포넌트는 현재 시간을 가지고 있는 객체를 가지고setState()
메소드를 호출함으로써 UI 업데이트를 계획한다.setState()
호출 덕분에 React는 state가 변경된 것을 알 수 있게 되고render()
메소드를 다시 호출하여 화면에 어떤 것이 표시되어야 하는지 알게 된다. 이 때는render()
내에 있는this.state.date
가 달라지고 렌더링 출력에 업데이트된 시간이 포함된다. 이를 통해 React는 DOM을 업데이트 한다. Clock
컴포넌트가 DOM에서 제거되면 React는componentWillUnmount()
lifecycle hook를 호출하게 되고 timer가 중지된다.
Using State Correctly
setState()
에 대해 반드시 알아야 할 사항이 3가지 있다.
Do Not Modify State Directly
아래 예제는 컴포넌트를 다시 렌더링하지 않는다.
// Wrong
this.state.comment = 'Hello';
대신에 setState()
를 사용해야 한다.
// Correct
this.setState({comment: 'Hello'});
this.state
를 이용하여 할당할 수 있는 유일한 위치는 생성자이다.
State Updates May Be Asynchronous
React는 성능 향상을 위해 다수의 setState()
호출을 하나의 업데이트로 일괄 처리한다.this.props
와 this.state
는 비동기방식으로 처리되기 때문에 이후에 올 state에 대한 계산 결과 값을
신뢰해서는 안된다.
예를들어 다음 코드는 카운터 업데이트에 실패할 것이다.
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
이 코드를 수정하기 위해서는 파라미터로 객체가 아닌 함수를 받는 두 번째 형식의 setState()
를 사용해야 한다.
파라미터로 전달되는 함수는 첫 번째 인자로 이전 state를, 두 번째 인자로는 업데이트된 시점의 props를 받는다.
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
위 코드에서는 화살표 함수를 사용했지만 일반 형식의 함수를 사용해도 된다.
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
State Updates are Merged
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
는 완전히 대체한다.
The Data Flows Down
부모 컴포넌트나 자식 컴포넌트 모두 어떤 컴포넌트가 state를 가지고 있는 컴포넌트인지 state가 없는 컴포넌트인지
알지 못한다. 또한 함수로 정의된 컴포넌트인지 클래스로 만들어진 컴포넌트인지도 알 수 없다.
이러한 이유로 state는 종종 local 또는 encapsulated라고 불리운다. state를 소유하고 설정하는 컴포넌트 외에는
state에 접근할 수 없다.
어떤 경우에는 자식 컴포넌트에 props를 통해 state를 전달하기도 한다.
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
이러한 방식은 사용자 정의 컴포넌트에서도 작동한다.
<FormattedDate date={this.state.date} />
FormattedDate
컴포넌트는 date
를 props로 전달받게 되는데, 이것이 Clock
의 state로부터 온 것인지,Clock
의 props로부터 온 것인지, 아니면 직접 손으로 타이핑을 해서 전달 받은 것인지 알지 못한다.
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
이 것을 일반적으로 “top-down” 또는 “unidirectional” data flow라고 부른다. 어떤 state든 반드시 특정
컴포넌트에 소유되어있어야 하고 그 state로부터 파생된 어떤 data 또는 UI라도 트리 구조의 아래쪽으로만 영향을
미칠 수 있다.
컴포넌트의 트리 구조를 props의 폭포로 생각한다면 각 컴포넌트의 state는 임의의 지점에서 합류하는 또다른
수원으로 생각할 수 있으며 이 또한 아래로 흐르게 되는 것이다.
모든 컴포넌트가 실제로 독립되어있다는 것을 보여주기 위해 3개의 <Clock>
을 가진 App
을 생성하였다.
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
각각의 Clock
은 자신이 가진 timer만을 설정하고 있으며 각각의 timer는 독립적으로 업데이트된다.
React App들에서는 stateful인지 stateless인지 하는 것은 시간이 지남에 따라 변경될 수 있는 세부 구현 사항으로
여겨진다. stateless 컴포넌트를 stateful 컴포넌트 내에서 사용할 수도 있고 그 반대도 가능하다.
'Study > React' 카테고리의 다른 글
React 살펴보기 #8 (0) | 2017.05.15 |
---|---|
React 살펴보기 #7 (0) | 2017.04.20 |
React 살펴보기 #5 (0) | 2017.03.04 |
React 살펴보기 #4 (0) | 2017.02.17 |
React 살펴보기 #3 (0) | 2017.02.11 |