이 섹션에서는 재사용가능하고 캡슐화된 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);
여기서 중요한 요구사항은 Clock이 타이머를 설정하고 매 초 UI를 업데이트 하는 것은 CLock의 구현 세부사항이어야 합니다.
Clock은 한 번만 작성하고 자체적으로 업데이트 시킵니다.
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
요구사항을 구현하기 위해선 Clock 컴포넌트에 "state"를 추가해야 합니다.
state는 props와 비슷하지만 컴포넌트에 의해 완전히 제어되며 private 속성입니다.
로컬 state는 클래스에서만 사용 가능합니다.
함수를 클래스로 변환
- ES6 class를 같은 이름으로 만들고, React.Component 를 확장합니다.
- 비어있는 render() 메서드를 하나 추가합니다.
- 함수의 바디를 render() 메서드 안으로 옮깁니다.
- render() 바디 내에서 props 를 this.props 로 바꿉니다.
- 남아있는 빈 함수 선언문을 제거합니다.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
{/*this.props는 부모 컴포넌트의 전달 값*/}
</div>
);
}
}
Clock은 함수 대신 클래스로 정의하여 로컬 state나 라이프사이클 훅 같은 추가 기능 사용 가능합니다.
Class에 로컬 state 추가하기
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>
{/*this.props를 this.state로 변경*/}
</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()};
}
클래스 컴포넌트는 항상 props와 함께 기본 생성자를 호출합니다.
3. <Clock /> 요소에서 date prop을 삭제합니다.
ReactDOM.render(
<Clock />,
{/*date prop 삭제*/}
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 이 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.props는 React 자체에 의해 설정되고 this.state는 특별한 의미가 있지만, 시각적 출력에 사용되지 않는 것을 지정해야 하는 경우 클래스에 수동으로 필드를 추가할 수 있습니다.
만약 render()내에서 사용하지 않는다면, state가 되어서는 안됩니다.
componentWillUnmount() 라이프사이클 훅에서 타이머를 종료합니다.
componentWillUnmount() {
clearInterval(this.timerID);
}
그 다음 tick()메서드를 구현해 봅니다.
this.setState()를 사용해서 컴포넌트 로컬 state에 대한 업데이트를 예약 합니다.
class Clock extends React.Component {
constructor(props) {
super(props);
//this.state 셋팅
this.state = {date: new Date()};
}
//렌더링 후 호출
//interval 이벤트 등록
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
//컴포넌트 제거된 후 호출
//렌더링 될 때 만들었던 interval 이벤트 종료
componentWillUnmount() {
clearInterval(this.timerID);
}
//componentDidMount() 이벤트에 등록 될 메서드 생성
//this.state에 들어갈 파라미터는 this.state이다.
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 컴포넌트의 생성자 함수를 호출합니다. Clock이 현 시간 화면에 보여질 때, 현 시간을 포함하는 this.state 객체를 초기화합니다. 이 state는 추후 업데이트합니다.
- React가 Clock 컴포넌트의 render() 메서드를 호출합니다. React가 어떤 걸 화면에 보여줘야 하는 지 배우는 방법입니다. 그다음 React는 Clock의 렌더링 출력과 일치하도록 DOM을 업데이트합니다.
- Clock 출력이 DOM에 주입되었을 때, React는 componentDidMount() 라이프 훅을 호출합니다. 내부에서, Clock 컴포넌트는 브라우저에게 컴포넌트의 tick() 메서드를 초당 한번 호출하는 타이머를 설정하라고 요구합니다.
- 브라우저에서 매 초마다 tick() 메서드를 호출합니다. 내부에서, Clock 컴포넌트는 현재 시간을 포함하는 객체로 setState() 를 호출하여 UI 업데이트를 예약합니다. setState() 호출 덕분에, React는 상태가 변경된 걸 알고 있고, render() 메서드를 다시 한번 호출해 스크린에 무엇이 있어야 하는 지 알 수 있습니다. 이번에는, render() 메서드 내의 this.state.date 가 달라지므로 렌더 출력에 업데이트된 시간이 포함됩니다. React는 그에 따라 DOM을 업데이트합니다.
- 만약 Clock 컴포넌트가 DOM에서 삭제되었다면, React는 componentWillUnmount() 라이프사이클 훅을 호출하고 타이머를 멈춥니다.
State 바르게 사용하기
State를 직접 수정하지 마세요.
// Wrong
this.state.comment = 'Hello';
//위 처럼 this.state를 직접 수정해서는 안된다.
// Correct
this.setState({comment: 'Hello'});
this.state를 지정할 수 있는 유일한 곳은 생성자 함수 내부입니다.
State 업데이트는 비동기 일 수 있습니다.
React는 여러 setState() 호출을 성능을 위해 단일 업데이트로 배치할 수 있습니다.
this.props 및 this.state 가 비동기로 업데이트될 수 있기 때문에, 다음 state를 계산할 때 해당 값을 신뢰해서는 안됩니다.
예를 들어, 카운터를 업데이트하는 이 코드는 실패할 수 있습니다.
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
//state의 업데이트는 비동기이므로 정확한 값이 보장이 안된다.
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
이 문제를 해결하기 위해 객체가 아닌 함수를 받는 두 번째 형식의 setState()를 사용할 수 있습니다. 이 함수는 이전 state를 첫번째 인수로 받고, 두번째 인수로 업데이트가 적용 될 때 props를 받습니다.
※setState 함수는 객체 or 콜백 함수(첫 번째 파라미터는 이전 props, 두 번째 파라미터는 변경할 props)로 파라미터를 받는다.
State 업데이트는 병합됩니다.
setState()를 호출할 때, React는 현재 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로 내려줄 수 있습니다.
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
위 코드는 유저가 정의한 컴포넌트에서도 동작합니다.
<FormattedDate date={this.state.date} />
FormattedDate 컴포넌트는 props에서 date 를 받지만 이 값이 Clock의 상태(state)인 지, Clock의 props인 지, 혹은 손으로 입력한 건지 알 수 없습니다. 왜냐하면 파라미터로 단순히 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가 없는 컴포넌트를 사용할 수 있으며, 그 반대 경우도 마찬가지입니다.
State와 라이프 사이클
'programming_kr > react' 카테고리의 다른 글
조건부 렌더링 (0) | 2021.01.21 |
---|---|
이벤트 제어 (0) | 2021.01.19 |
컴포넌트와 props (0) | 2021.01.13 |
요소의 렌더링 (0) | 2021.01.10 |
JSX란? (0) | 2021.01.08 |
댓글