4. state와 생명 주기
목표
- state와 생명 주기 대해 톺아본다.
4. state와 생명 주기
-
props는 다음과 같은 주의사항이 있었다.
모든 리액트 컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 동작해야 합니다.
-
하지만, 컴포넌트 안에서 변경해야만 하는 값이 있을 수 있다. 이러한 경우 때문에 리액트에서는 state라는 것을 제공한다.
-
state에 대해 알아보기 위해 매 초마다 현재 시각을 출력하는 Clock 컴포넌트를 구현하면 다음과 같다.
4-1. state 추가하기
// 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 컴포넌트에 new Date()값을 props로 넘겨주어 현재 시각을 나타낸다.
- 하지만, props를 넘겨주는 것만으로는 매 초마다 현재 시각을 나타낼 수 없다.
🤔 의문
- 어떻게 하면 매 초마다 현재 시간을 나타낼 수 있을까?
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를 사용하려면 다음과 같은 과정을 따라야 한다.
- 먼저, 초기 this.state를 지정하는 class constructor를 추가한다.
- 그 후 this.props.date를 this.state.date로 바꿔준다.
- 참고로 클래스 컴포넌트는 항상 props로 기본 constructor를 호출해야 한다.
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>
);
}
}
- 하지만, 이것만으로는 매 초마다 변하는 시각을 나타낼 수 없다.
4-2. 생명주기 추가하기
- Clock이 처음 DOM에 렌더링 될 때마다 타이머를 설정하려고 한다.
- 이것을 리액트에서는 마운팅이라고 한다.
- 클래스 컴포넌트에서는 componentDidMount()를 이용해 마운팅할 때 원하는 기능을 추가할 수 있다.
- 먼저, date 값을 업데이트 해주는 tick() 함수를 만들고 마운팅할 때 매 초마다 이 tick 함수를 호출해준다.
class Clock extends React.Component {
// 생략
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
tick() {
this.setState({
date: new Date()
});
}
// 생략
}
- 그리고 Clock에 의해 생성된 DOM이 삭제될 때마다 타이머를 해제해야 한다.
- 이를 리액트에서 언마운팅이라고 한다.
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>
);
}
- 함수형 컴포넌트는 constructor 대신 useState를 이용해 state 값을 초기화한다.
- 또한, componentDidMount()와 componentWillUnmount() 대신 useEffect를 사용해 마운팅, 언마운팅 될 때 원하는 기능을 추가할 수 있다.
4-3. State
- state를 사용할 때에는 다음과 같은 주의사항이 있다.
4-3-1. 직접 State를 수정하지 말아야 한다.
this.state.comment = 'Hello';
- 위 코드는 컴포넌트를 다시 렌더링하지 않기 때문에 화면에 있는 출력값이 변하지 않는다.
this.setState({comment: 'Hello'});
// or
setState({comment: 'Hello'});
- this.setState 혹은 함수형 컴포넌트라면 setState를 사용해야 한다.
4-3-2. State 업데이트는 비동기적일 수도 있다.
- 리액트는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리할 수 있다.
this.setState({
counter: this.state.counter + this.props.increment,
});
- 위 코드는 업데이트에 실패할 수 있다.
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
- 객체보다는 함수를 인자로 사용하는 다른 형태의 setState()를 사용하는 것이 안전하다.
- 이전 state를 첫 번째 인자로 받고, 업데이트가 적용된 시점의 props를 두 번째 인자로 받기 때문이다.
- 이것은 리액트 훅을 사용할 때 setState를 할 때 이전 값인 prev를 사용하는 것도 마찬가지이다.
const onClick = () => {
setState((prev) => !prev);
};
출처
Author And Source
이 문제에 관하여(4. state와 생명 주기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@dev0408/react-official-document-4저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)