State
- 리액트 컴포넌트의 상태, 변경 가능한 데이터
- state를 정의할 때 중요한 것은 렌더링이나 데이터 흐름에 사용되는 값만 state에 포함시켜야 함
- state가 변경될 경우 컴포넌트가 재렌더링되기 때문에 렌더링과 데이터 흐름에 관련없는 값을 포함하면 컴포넌트가 다시 렌더링되어 성능을 저하시킬 수 있기 때문
- 리액트의 state는 자바스크립트 객체
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = {
liked: false
};
}
...
}
- state는 직접적인 변경이 불가능함
- state를 변경하기 위해서는
setState()
함수를 사용
this.state = {
name: 'Jieun'
};
this.setState({
name: 'Jieun'
});
생명주기 함수 (Lifecycle method)
- 컴포넌트는 계속 존재하는 것이 아니라 시간의 흐름에 따라 생성되고 업데이트되다가 사라짐
- 마운트(Mount)
- 컴포넌트가 생성되는 시점
- 컴포넌트의 constructor(생성자)가 실행
- 생성자는 컴포넌트의 state를 정의
- 컴포넌트가 렌더링된 이후에는
componenetDidMount()
함수가 호출됨
- 업데이트(Update)
- 컴포넌트의 props가 변경될 때
setState()
함수 호출에 의해 state가 변경될 때
forceUpdate()
라는 강제 업데이트 함수 호출로 인해 컴포넌트가 다시 렌더링될 때
- 렌더링 이후
componentDidUpdate()
가 함수 호출됨
- 언마운트(Unmount)
- 상위 컴포넌트에서 현재 컴포넌트를 더 이상 화면에 표시하지 않게 될 때
- 언마운트 직전에
componentWillUnmount()
함수가 호출됨
실습
State 사용하기
- create-react-app을 이용해 프로젝트 생성후 Notificaltion.jsx라는 이름의 파일 만들기
- Notification이라는 이름의 리액트 클래스 컴포넌트 만들기
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;
- 위에서 작성한 Notification 컴포넌트는 state에 아직 아무런 데이터도 가지고 있지 않음
- Notification 컴포넌트를 목록 형태로 보여주기 위한 NotificationList 컴포넌트 만들기
- 처음 생성자에서 앞으로 사용할 데이터를 state에 넣어 초기화
- notifications라는 이름의 빈 배열을 state에 넣기
componentDidMount()
함수에서 자바스크립트 setInterval()
함수를 사용하여 매 1000ms 초마다 정해진 작업 수행
- 미리 만들어둔 알림 데이터 배열
reversedNotifications
로부터 알림 데이터를 하나씩 가져와 state에 있는 notifications 배열에 넣고 업데이트 ( setState()
함수 사용 )
import React from "react";
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);
}
componentWillUnmount() {
if (timer) {
clearInterval(timer);
}
}
render() {
return (
<div>
{this.state.notifications.map((notification) => {
return (
<Notification
message={notification.message}
/>
);
})}
</div>
);
}
}
export default NotificationList;
- 새로 만든 Notification 컴포넌트를 실제 화면에 렌더링 하기 위해 index.js 파일 수정하기
import React 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 설치하기
- 설치가 완료되면 개발자 도구에 컴포넌트(Components)와 프로파일러(Profiler)라는 새로운 탭이 생김
- Components 탭을 눌러보면 아래 그림처럼 현재 화면에 존재하는 컴포넌트가 트리 형태로 보이며, 각 컴포넌트별로 props와 state도 확인 가능
- React Developer Tools의 프로파일러 탭에서는 컴포넌트들이 렌더링되는 과정을 기록하여 각 단계별로 살펴볼 수 있음
- 이 기능을 이용하면 어떤 컴포넌트가 렌더링 되는지, 렌더링 시간이 얼마나 소요되었는지 그리고 컴포넌트가 왜 다시 렌더링되었는지 등을 확인할 수 있음
생명주기 함수 사용해보기
- 이번에는 위에서 만든 Notification 컴포넌트에 각 생명주기 함수를 사용해보자.
- 먼저 세 가지의 생명주기 함수들이 호출될 경우 콘솔에 로그를 남기도록 아래와 같이 코드를 작성한다.
- 각각 컴포넌트가 마운트된 이후, 컴포넌트가 업데이트된 이후 그리고 컴포넌트가 언마운트되기 전에 호출된다.
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(`componentDidMount() called.`);
}
componentDidUpdate() {
console.log(`componentDidUpdate() called.`);
}
componentWillUnmount() {
console.log(`componentWillUnmount() called.`);
}
render() {
return (
<div style={styles.wrapper}>
<span style={styles.messageText}>{this.props.message}</span>
</div>
);
}
}
export default Notification;
- 콘솔 탭을 확인해보면 작성한 로그들을 볼 수 있지만 로그가 중복되어 구분이 힘들다.
- 로그에 아이디가 함께 나오게 하기 위해 각 알림 객체에 아이디를 넣어준다.
- Notification 컴포넌트에 전달할 props에 키와 id를 추가한다.
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 {
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;
- Notification 컴포넌트의 로그를 아이디와 함께 출력하도록 아래와 같이 수정한다.
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;
- 이제 콘솔로그를 다시 확인해보면 순서대로 로그가 나오는 것을 볼 수 있다.
- 그런데 우리가 사용한 세 가지 생명주기 함수 중에서
componentWillUnmount()
함수의 로그가 보이지 않는다.
- 모든 컴포넌트가 마운트만 되고 언마운트되지 않았기 때문!!!
- 언마운트 로그를 보기 위해 NotificationList 컴포넌트에서 매초 알림을 추가하는 부분에 알림 추가가 모두 끝나면 notifications 배열을 비우도록 수정해보자.
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;
- 이제 모든 컴포넌트에 대해 언마운트 로그가 나오는 것을 볼 수 있다.
실습 전체 코드
References