목표
- state와 생명 주기 대해 톺아본다.
props는 다음과 같은 주의사항이 있었다.
모든 리액트 컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 동작해야 합니다.
하지만, 컴포넌트 안에서 변경해야만 하는 값이 있을 수 있다. 이러한 경우 때문에 리액트에서는 state라는 것을 제공한다.
state에 대해 알아보기 위해 매 초마다 현재 시각을 출력하는 Clock 컴포넌트를 구현하면 다음과 같다.
// Clock.jsx
import React, { Component } from "react";
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
// App.jsx
import Clock from "./Clock";
export default function App() {
return (
<>
<Clock date={new Date()} />
</>
);
}
🤔 의문
- 어떻게 하면 매 초마다 현재 시간을 나타낼 수 있을까?
Clock 컴포넌트에 State를 활용하면 나타낼 수 있다. State는 props와 유사하지만 비공개이며 컴포넌트에 의해 완전히 제어된다.
props 대신 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>
);
}
}
class Clock extends React.Component {
// 생략
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
tick() {
this.setState({
date: new Date()
});
}
// 생략
}
componentWillUnmount() {
clearInterval(this.timerID);
}
그럼 다음과 같이 매 초마다 시각이 갱신된다.
간단한 코드이지만 리액트는 많은 일을 하고 있다. 위 코드는 다음과 같이 동작한다.
Clock 컴포넌트가 ReactDOM.render()로 전달되었을 때 리액트는 Clock 컴포넌트의 constructor를 호출한다. 그 후 this.state를 초기화 한다.
리액트는 Clock 컴포넌트의 render() 메서드를 호출한다. 그 후 리액트는 DOM을 업데이트한다.
Clock 컴포넌트의 출력값이 DOM에 삽입되면, 리액트는 componentDidMount() 생명주기 메서드를 호출한다. 그 안에서 Clock 컴포넌트는 매초 tick() 메서드를 호출하기 위한 타이머를 설정하도록 브라우저에 요청한다.
매초 브라우저가 tick() 메서드를 호출하면 그 안에서 Clock 컴포넌트는 setState()에 현재 시각을 포함하는 객체를 호출하면서 UI 업데이트를 진행한다. setState() 호출하면 리액트는 state가 변경된 것을 인지하고 render() 메서드를 다시 호출한다. 이에 따라 DOM을 업데이트 한다.
Clock 컴포넌트가 DOM으로부터 한 번이라도 삭제된 적이 있다면 리액트는 타이머를 멈추기 위해 componentWillUnmount() 생명주기 메서드를 호출한다.
import { useEffect, useState } from "react";
export default function Clock() {
const [date, setDate] = useState(new Date());
useEffect(() => {
const timerID = setInterval(() => tick(), 1000);
return () => {
clearInterval(timerID);
};
}, []);
function tick() {
setDate(new Date());
}
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {date.toLocaleTimeString()}.</h2>
</div>
);
}
this.state.comment = 'Hello';
this.setState({comment: 'Hello'});
// or
setState({comment: 'Hello'});
this.setState({
counter: this.state.counter + this.props.increment,
});
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
const onClick = () => {
setState((prev) => !prev);
};