이 페이지에서는 React 컴포넌트의 상태 및 수명주기 개념을 소개합니다. 자세한 컴포넌트 API 참조는 여기에서 찾을 수 있습니다 .
이전 섹션 에서 보았던 시계를 살펴봅니다.
Rendering Elements 에서는 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);
이 섹션에서는 시계 컴포넌트를 실제로 재사용가능하고 캡슐화 하는 방법을 학습합니다. 자체 타이머를 설정하고 매 초마다 업데이트합니다.
시계가 어떻게 보이는지 캡슐화하는 것부터 시작합니다.
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);
그러나 중요한 요구 사항이 빠져 있습니다. Clock
이 타이머를 설정하고 매 초 UI를 업데이트 하는 것은 Clock 의 구현 세부사항이어야 합니다.
이상적인 것은 Clock
을 한번만 작성하고 자체적으로 업데이트 시키는 것입니다.
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
이를 구현하려면 Clock
구성 요소에 state를 추가해야합니다 .
State
는 props
와 비슷하지만 컴포넌트에 의해 완전히 제어되며 private 속성
입니다.
이전에 언급 했던 대로 클래스로 정의한 컴포넌트에는 몇가지 추가 기능이 있습니다. 로컬 state는 클래스에서만 사용 가능한 기능입니다.
Clock
같은 함수형 컴포넌트를 클래스로 변환하려면 다섯 단계를 진행합니다.
React.Component
를 extends
합니다.render()
를 추가하십시오.render()
메서드로 옮깁니다.class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Clock
은 이제 함수가 아닌 클래스로 정의됩니다.
render()
는 업데이트가 발생할 때마다 호출되지만 <Clock />
은 동일한 DOM 노드로 렌더링 하는 한 Clock클래스의 단일 인스턴스만 사용됩니다. 이를 통해 로컬 stae 및 LifeCycle 메소드와 같은 추가 기능을 사용할 수 있습니다.
이를 통해 로컬 state나 라이프사이클 훅 같은 추가 기능을 사용할 수 있습니다.
date 를 props에서 state로 옮기기 위해서 세 단계를 진행합니다.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
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에 전달하는 방법을 확인하세요.
```
constructor(props) {
super(props);
this.state = {date: new Date()};
}
```
- 클래스 컴포넌트는 항상 props 와 함께 기본 생성자를 호출합니다.
<Clock />
요소에서 date prop을 삭제합니다.ReactDOM.render(
<Clock />,
document.getElementById('root')
);
나중에 타이머 코드를 구성 요소 자체에 다시 추가합니다.
최종적으로 다음과 같은 코드가 됩니다.
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
에 자체 타이머를 설정하고 매 초마다 업데이트 할 것입니다.
많은 컴포넌트로 구성되어있는 어플리케이션에서는 컴포넌트가 제거될 때 사용 된 리소스를 확보하는 것이 매우 중요합니다.
Clock
이 DOM에 최초로 렌더링 될 때 타이머를 설정 하려고합니다. React에서 이를 "mounting"이라고 합니다.
그리고 DOM에서 Clock
을 삭제 했을때 타이머를 해제 하려고 합니다. 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>
);
}
}
이런 메서드들을 “라이프사이클 훅” 이라고 부릅니다.
componentDidMount()
훅은 컴퓨넌트 출력이 DOM에 렌더링 된 후에 동작합니다. 타이머를 설정하는 좋은 시점입니다.
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
this.에 타이머 아이디를 어떻게 설정하는지 살펴봅시다.
this.props
는 React 자체에 의해 설정되고 this.state
는 특별한 의미가 있지만, 데이터 흐름에 사용되지 않는 항목(예: 타이머 아이디)을 저장해야 하는 경우 클래스에 수동으로 필드를 추가 할 수 있습니다.
componentWillUnmount()
라이프 사이클 훅 에서 타이머를 제거합니다 .
componentWillUnmount() {
clearInterval(this.timerID);
}
마지막으로 Clock
컴포넌트에서 매 초마다 동작하는 tick()
이라는 메서드를 구현해봅시다.
this.setSTate()
를 사용해서 컴포넌트 로컬 state에 대한 업데이트를 예약합니다.
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')
);
이제 시계는 매 초 깜빡입니다.
어떤 작업을 했는 지와 메서드가 호출되는 순서를 간단히 요약해봅시다.
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.comments
는 완전히 대체 되고, this.state.posts
는 그대로 유지됩니다.
부모 컴포넌트나 자식 컴포넌트는 특정 컴포넌트의 state 유무를 알 수 없으며 해당 컴포넌트가 함수나 클래스로 선언되었는지 알 수 없습니다.
부모 컴포넌트나 자식 컴포넌트는 특정 컴포넌트의 state 소유 여부를 알 수 없으며 함수 또는 클래스로 정의되는지 알 수 없습니다.
이것이 state가 종종 로컬이라 불리우거나 캡슐화 된 이유입니다. state를 소유한 컴포넌트외에는 액세스 할 수 없습니다.
컴포넌트는 자신의 state를 자식 컴포넌트로 전달할 수 있습니다.
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
이 코드는 사용자 저으이 컴포넌트에서도 동작합니다.
<FormattedDate date={this.state.date} />
FormattedDate
컴포넌트는 props
로 date
를 받지만 이 값이 Clock
의 state인 지, Clock
의 props
인 지, 혹은 입력한 값인지 알 수 없습니다.
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
이런 데이터 흐름을 보통 “하향식(top-down)” 혹은 “단방향(unidirectional)” 데이터 흐름이라고 합니다. 모든 state는 항상 특정 컴포넌트가 가지며, 해당 state에서 파생된 모든 데이터 또는 UI는 트리의 컴포넌트 “아래(below)“에만 영향을 미칩니다.
컴포넌트 트리를 props의 폭포라고 상상해보면, 각 컴포넌트의 상태는 임의의 지점에서 추가되는 물과 비슷하지만 또한 아래로 흐릅니다.
모든 컴포넌트가 실제로 분리되어있음을 보여주기 위해, 세개의 <Clock>
을 렌더링하는 App 컴포넌트를 만들어봅시다.
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
각 Clock
은 자체적으로 타이머를 생성하고 독립적으로 업데이트합니다.
React 앱에서 컴포넌트가 state 소유 여부는 시간이 지남에 따라 바뀔 수 있는 컴포넌트의 구현 세부 사항으로 간주합니다. state를 가진 컴포넌트 내부에서 state가 없는 컴포넌트를 사용할 수 있으며, 그 반대 경우도 마찬가지입니다.