2023.01.30 State and Lifecycle
state는 상태라는 뜻을 갖고 있다.
리액트에서 state는 리액트 컴포넌트의 상태를 의미한다.
다만 상태라는 단어가 정상인지 비정상인지를 나타내는 것이라기 보다
리액트 컴포넌트의 데이터라는 의미에 더 가깝다.
쉽게 말하면 리액트 컴포넌트의 변경 가능한 데이터를 state 라고 부른다.
state는 사전에 미리 정해진 것이 아니라 리액트 컴포넌트를 개발하는 가 개발자가 직접 정의해서 사용하게 된다.
state를 정의할 때 중요한 점은 꼭
렌더링이나 데이터 흐름에 사용되는 값만 state에 포함시켜야한다는 것이다.
왜냐하면 state가 변경될 경우 컴포넌트가 재렌더링되기 때문에
렌더링과 데이터 흐름에 관련없는 값을 포함하면 컴포넌트가 다시 렌더링되어 성능을
저하 시킬 수 있다.
그래서 렌더링과 데이터 흐름에 관련 있는 값만 state에 포함하도록 해야하며,
그렇지 않은 값은 컴포넌트 인스턴스의 필드로 정의하면 된다.
state는 그냥 하나의 자바스크립트 객체이다.
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = {
liked : false
};
}
...
}
이 코드는 LikeButton이라는 리액트 클래스 컴포넌트를 나타낸 것이다.
모튼 클래스 컴포넌트에는 constructor라는 이름의 함수가 존재하는데 생성자라는 의미를 갖고 있으며,
클래스가 생성될 때 실행되는 함수이다.
this.state라는 부분이 바로 현재 컴포넌트의 state를 정의하는 부분이다.
클래스 컴포넌트의 경우 state를 생성자에서 정의한다.
이렇게 정의한 state는 정의된 이후 일반적인 자바스크립트 변수를 다루듯이 직접 수정할 수 없다.
//state를 직접 수정(잘못된 방법)
this.state = {
name: 'Inje'
};
// setState 함수를 통한 수정 (정상적인 사용법)
this.setState({
name: 'Inje'
});
state는 직접적인 변경이 불가능하다.
state를 변경하고자 할 때는 꼭 setState( )라는 함수를 사용해야 한다.
위 그림을 보면 크게 출생, 인생, 사망으로 나누어져 있다.
각 과정의 하단에 초록색으로 표시된 부분은 생명주기에 따라 호출되는
클래스 컴포넌트의 함수이다.
Lifecycle method라고 부르며 생명주기 함수이다.
이때 컴포넌트의 생성자가 실행되고 생성자에서 컴포넌트의 state를 정의하게 된다.
또한 컴포넌트가 렌더링되며 이후에 componentDidMount( ) 함수가 호출된다.
업데이트 과정에서는 컴포넌트의 props가 변경되거나 setState( )함수 호출에 의해
state가 변경되거나, forceUpdate( )라는 강제 업데이트 함수 호출로 인해
컴포넌트가 다시 렌더링 된다.
그리고 렌더링 이후에 componentDidUpdate( )함수가 호출된다.
상위 컴포넌트에서 현재 컴포넌트를 더 이상 화면에 표시하지 않게 될 때 언마운트 된다고 볼 수 있다.
이 때 언마운트 직전에 componentWillUnmount( ) 함수가 호출된다.
컴포넌트 생명주기에서 기억해야 할 부분은
컴포넌트가 계쏙 존재하는 것이 아니라
시간의 흐름에 따라 생성되고 업데이트되다가 사라진다는 것이다.
이것은 비단 클래스 컴포넌트뿐만 아니라, 함수 컴포넌트에도 해당하는 내용이다.
import React from "react";
const styles = {
wrapper: {
margin: 8,
padding: 8,
display: "flex",
flexDirection: "row",
border: "1px solid grey",
borderRadius: 16,
},
messageText: {
color: "black",
fontSize: 16,
},
};
class Notification extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div style={styles.wrapper}>
<span style={styles.messageText}>
{this.props.message}
</span>
</div>
);
}
}
export default Notification;
위의 코드에서 constructor 부분을 보면 알겠지만
Notification 컴포넌트는 state에 아무런 데이터도 가지고 있지 않다.
NotificationList 컴포넌트는 Notification 컴포넌트를 목록 형태로 보여주기 위한 컴포넌트이다.
import Notification from "./Notification";
const reservedNotifications = [
{
message: "안녕하세요, 오늘 일정을 알려드립니다.",
},
{
message: "점심식사 시간입니다.",
},
{
message: "이제 곧 미팅이 시작됩니다.",
},
];
var timer;
class NotificationList extends React.Component {
constructor(props) {
super(props);
this.state = {
notifications: [],
};
}
componentDidMount() {
const { notifications } = this.state;
timer = setInterval(() => {
if (notifications.length < reservedNotifications.length) {
const index = notifications.length;
notifications.push(reservedNotifications[index]);
this.setState({
notifications: notifications,
});
} else {
clearInterval(timer);
}
}, 1000);
}
render() {
return (
<div>
{this.state.notifications.map((notification) => {
return (
<Notification
message={notification.message}
/>
);
})}
</div>
);
}
}
export default NotificationList;
이 컴포넌트에서 눈여겨봐야 할 곳이 바로 state를 선언하고 사용하는 부분이다.
notifications라는 이름의 빈 배열을 state에 넣은 것을 볼 수 있다.
이처럼 생성자에서는 앞으로 사용할 데이터를 state에 넣어서 초기화한다.
constructor(props) {
super(props);
this.state = {
notifications: [],
};
그리고 클래스 컴포넌트의 생명주기 함수 중 하나인 componentDidmount () 함수에서는 자바스크립트의 setInterval () 함수를 사용하여 매 1000ms(1초)마 다 정해진 작업을 수행하고 있습니다.
이 작업은 미리 만들어둔 알림 데이터 배열 인 reservedNotifications로부터 알림 데이터를 하나씩 가져와서 state에 있는 notifications 배열에 넣고 업데이트하는 것입니다.
여기에서 주목해서 봐야 할 부분 은 state를 업데이트하기 위해서 setstate () 함수를 사용한다는 것입니다.
위에서 배운 것처럼 클래스 컴포넌트에서 state를 업데이트하려면 반드시 setstate () 함수 를 사용해야 합니다.
notifications.push(reservedNotifications[index]);
this.setState({
notifications: notifications,
});
수정
import React, { Profiler } from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import NotificationList from './chapter_06/NotificationList';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<NotificationList />
</React.StrictMode>
);
reportWebVitals();
처음에는 화면에 아무것도 안 보이다가 매초 알림이 하나씩 추가되는 것을 확인할 수 있다.
리액트 애플리케이션을 개발할 때에는 크롬 개발자 도구의 엘리먼트 탭을 통해서 확인 하는 것보다 리액트를 위해서 별도로 개발된 React Developer Tools(리액트 개발 자 도구)라는 도구를 이용하는 것이 더 좋다.
설치를 위해 아래 그림처럼 구글에서 react developer tools를 검색한다.
또한 React Developer Tools의 프로파일러 탭에서는 컴포넌트들이 렌더링되는 과정 을 기록하여 각 단계별로 살펴볼 수 있다.
이 기능을 이용하면 어떤 컴포넌트가 렌 더링 되는지, 렌더링 시간이 얼마나 소요되었는지 그리고 컴포넌트가 왜 다시 렌더링되 었는지 등을 확인할 수 있다.
이를 통해 불필요하게 렌더링되거나 무거운 컴포넌트 를 찾아서 최적화함으로써 리액트 애플리케이션의 성능을 향상시킬 수 있다.
지금부터 위에서 만든 Notification 컴포트에 각 생명주기 함수를 사용해 보자.
먼저 아래와 같이 세 가지의 생명주기 함수들이 호출될 경우 콘솔에 로그를 남기 도록 코드를 작성한다.
각각 컴포넌트가 마운트된 이후. 컴포넌트가 업데이트된 이후 그리고 컴포넌트가 언마운트되기 전에 호출될 것이다.
componentDidMount() {
console.log(`${this.props.id} componentDidMount() called.`);
}
componentDidUpdate() {
console.log(`${this.props.id} componentDidUpdate() called.`);
}
componentWillUnmount() {
console.log(`${this.props.id} componentWillUnmount() called.`);
}
로그가 구분되어 보일 수 있게 id를 추가
const reservedNotifications = [
{
id: 1,
message: "안녕하세요, 오늘 일정을 알려드립니다.",
},
{
id: 2,
message: "점심식사 시간입니다.",
},
{
id: 3,
message: "이제 곧 미팅이 시작됩니다.",
},
];
그리고 아래 코드처럼 Notification 컴포넌트에 전달할 props에 키와 id를 추가해준다.
여기서 키는 리액트 엘리먼트를 구분하기 위한 고유의 값인데
map( ) 함수를 사용할 때에는 필수적으로 들어가야 한다.
return (
<Notification
key={notification.id}
id={notification.id}
message={notification.message}
/>
);
이후 브라우제에서 콘솔 로그를 다시 확인해 보면
그림과 같이 순서대로 로그가 나오는 것을 볼 수 있다.
그런데 세 가지 생명주기 함수 중에서 componentWillUnmount( )함수의 로그가
보이지 않는다.
그 이유는 모든 컴포넌트가 마운트만 되고 언마운트되지 않았기 때문이다.
언마운트 로그를 보기 위해 NotificationLIst 컴포넌트에서
매초 알림을 추가하는 부분에 알림 추가가 모두 끝나면 notifications 배열을
비우도록 수정하자.
else {
this.setState({
notifications: [],
});
clearInterval(timer);
}
이렇게 하면 componentWillUnmount( )함수의 로그 확인이 가능하다.
✨ 전체코드
Notification.jsx
import React from "react";
const styles = {
wrapper: {
margin: 8,
padding: 8,
display: "flex",
flexDirection: "row",
border: "1px solid grey",
borderRadius: 16,
},
messageText: {
color: "black",
fontSize: 16,
},
};
class Notification extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
console.log(`${this.props.id} componentDidMount() called.`);
}
componentDidUpdate() {
console.log(`${this.props.id} componentDidUpdate() called.`);
}
componentWillUnmount() {
console.log(`${this.props.id} componentWillUnmount() called.`);
}
render() {
return (
<div style={styles.wrapper}>
<span style={styles.messageText}>
{this.props.message}
</span>
</div>
);
}
}
export default Notification;
NotificationList.jsx
import React from "react";
import Notification from "./Notification";
const reservedNotifications = [
{
id: 1,
message: "안녕하세요, 오늘 일정을 알려드립니다.",
},
{
id: 2,
message: "점심식사 시간입니다.",
},
{
id: 3,
message: "이제 곧 미팅이 시작됩니다.",
},
];
var timer;
class NotificationList extends React.Component {
constructor(props) {
super(props);
this.state = {
notifications: [],
};
}
componentDidMount() {
const { notifications } = this.state;
timer = setInterval(() => {
if (notifications.length < reservedNotifications.length) {
const index = notifications.length;
notifications.push(reservedNotifications[index]);
this.setState({
notifications: notifications,
});
} else {
this.setState({
notifications: [],
});
clearInterval(timer);
}
}, 1000);
}
componentWillUnmount() {
if (timer) {
clearInterval(timer);
}
}
render() {
return (
<div>
{this.state.notifications.map((notification) => {
return (
<Notification
key={notification.id}
id={notification.id}
message={notification.message}
/>
);
})}
</div>
);
}
}
export default NotificationList;
생명주기 함수의 경우 지금은 거의 사용하지 않는 클래스 컴포넌트의 기능이기 때문에
꼭 알고 있을 필요는 없다.
하지만 리액트 컴포넌트가 생성되어 마운트되고 업데이트를 거쳐서
언마운트 되기까지의 흐름은 여전히 중요한 부분이다.
State란?
State의 특징
마운트
업데이트
언마운트
컴포넌트는 계속 존재하는 것이 아니라
시간의 흐름에 따라 생성되고 업데이트되다가 사라지는 과정을 겪음