[React] State와 생명주기

문지은·2023년 7월 13일
0

React

목록 보기
5/24
post-thumbnail

State

  • 리액트 컴포넌트의 상태, 변경 가능한 데이터
  • state를 정의할 때 중요한 것은 렌더링이나 데이터 흐름에 사용되는 값만 state에 포함시켜야 함
    • state가 변경될 경우 컴포넌트가 재렌더링되기 때문에 렌더링과 데이터 흐름에 관련없는 값을 포함하면 컴포넌트가 다시 렌더링되어 성능을 저하시킬 수 있기 때문
  • 리액트의 state는 자바스크립트 객체
class LikeButton extends React.Component {
	constructor(props) {
    	super(props);
      	// state 정의
      	this.state = {
        	liked: false
        };
    }
  ...
}
  • state는 직접적인 변경이 불가능함
    • state를 변경하기 위해서는 setState() 함수를 사용
// state를 직접 수정 (잘못된 사용법)
this.state = {
  name: 'Jieun'
};

// setState 함수를 통한 수정 (정상적인 사용법)
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이라는 이름의 리액트 클래스 컴포넌트 만들기
// src/chapter_06/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 = {};
    }

    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() 함수 사용 )
// src/chapter_06/NotificationList.jsx

import React from "react";
import Notification from "./Notification";

const reservedNotifications = [
    {
        message: "안녕하세요, 오늘 일정을 알려드립니다.",
    },
    {
        message: "점심식사 시간입니다.",
    },
    {
        message: "이제 곧 미팅이 시작됩니다.",
    },
];

var timer;

class NotificationList extends React.Component {
    constructor(props) {
        super(props);
		
       // state 선언
        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 파일 수정하기
// src/index.jsx

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(리액트 개발자 도구)라는 도구를 이용하는 것이 더 좋음
  • 크롬 웹스토어에서 React Developer Tools 설치하기
    • 설치가 완료되면 개발자 도구에 컴포넌트(Components)와 프로파일러(Profiler)라는 새로운 탭이 생김
  • Components 탭을 눌러보면 아래 그림처럼 현재 화면에 존재하는 컴포넌트가 트리 형태로 보이며, 각 컴포넌트별로 props와 state도 확인 가능
  • React Developer Tools의 프로파일러 탭에서는 컴포넌트들이 렌더링되는 과정을 기록하여 각 단계별로 살펴볼 수 있음
    • 이 기능을 이용하면 어떤 컴포넌트가 렌더링 되는지, 렌더링 시간이 얼마나 소요되었는지 그리고 컴포넌트가 왜 다시 렌더링되었는지 등을 확인할 수 있음

생명주기 함수 사용해보기

  • 이번에는 위에서 만든 Notification 컴포넌트에 각 생명주기 함수를 사용해보자.
  • 먼저 세 가지의 생명주기 함수들이 호출될 경우 콘솔에 로그를 남기도록 아래와 같이 코드를 작성한다.
    • 각각 컴포넌트가 마운트된 이후, 컴포넌트가 업데이트된 이후 그리고 컴포넌트가 언마운트되기 전에 호출된다.
// src/chapter_06/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(`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를 추가한다.
// src/chapter_06/NotificationList.jsx

import React from "react";
import Notification from "./Notification";

const reservedNotifications = [
    {
        id: 1,  // id 추가
        message: "안녕하세요, 오늘 일정을 알려드립니다.",
    },
    {
        id: 2,  // id 추가
        message: "점심식사 시간입니다.",
    },
    {
        id: 3,  // id 추가
        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
                          	// props에 key와 id 추가
                            key={notification.id}
                            id={notification.id}
                            message={notification.message}
                        />
                    );
                })}
            </div>
        );
    }
}

export default NotificationList;
  • Notification 컴포넌트의 로그를 아이디와 함께 출력하도록 아래와 같이 수정한다.
// src/chapter_06/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() {
      	// props.id 추가
        console.log(`${this.props.id} componentDidMount() called.`);
    }

    componentDidUpdate() {
        // props.id 추가
        console.log(`${this.props.id} componentDidUpdate() called.`);
    }

    componentWillUnmount() {
        // props.id 추가
        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 배열을 비우도록 수정해보자.
// src/chapter_06/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 {
              	// notifications 배열 비우기
                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

profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈

0개의 댓글