컴포넌트 생명주기
- 컴포넌트의 생명주기란 컴포넌트의 생성부터 소멸까지의 과정을 말함
- 컴포넌트는 생명주기마다 함수를 가지고 있어서 이 함수들을 이용하면 특정 시점에 원하는 동작을 하도록 만들 수 있음
- 생명주기 함수
- 생명주기 함수는 render() 함수를 포함한 총 8개의 함수가 있음
- 생명주기 함수는 리액트 엔진에서 자동으로 호출함
- 따라서, 개발자가 마음대로 생명주기 함수를 호출할 수 없음
└ 생성 과정 : 컴포넌트 생성부터 생성 완료까지 4개의 생명주기 함수가 호출됨
└ 갱신 과정 : 생성 완료부터 갱신 완료까지 5개의 생명주기 함수가 호출됨
이 때, 갱신 과정은 shouldComponentUpdate() 함수의 반환값이 true이면 이후 과정이 진행되고,
shouldComponentUpdate() 함수의 반환값이 false이면 이후 과정이 모두 생략됨
└ 소멸 과정 : 갱신 완료부터 소멸 완료까지 1개의 생명주기 함수가 호출됨
- constructor(props) 함수 :
- consturctor() 함수는 맨 처음에 생성될 때 한번만 호출됨
- 상태(state 또는 객체 변수)를 선언할 때 사용됨
- constructor() 함수를 정의할 때는 항상 super() 함수를 가장 위에 호출해야 함
- super() 함수에는 프로퍼티와 생명 주기 상태 등을 초기화하는 과정을 포함하고 있음
- constructor(props) 함수 사용 예시 :
constructor(props) { super(props); // 이후에 추가적인 state 데이터 또는 변수를 선언함 }
- render() 함수 :
- render() 함수는 데이터가 변경되어 새 화면을 그려야 할 때 자동으로 호출됨
- render() 함수가 반환하는 JSX를 화면에 그려줌
- static getDerivedStateFromProps(props, state) 함수 :
- getDerivedStateFromProps() 함수는 정적 함수
- 함수 안에서 this.props나 this.state와 같은 방법으로 프로퍼티나 state 값에 접근할 수 없음
- 각 값에 접근하고 싶으면 반드시 인자로 전달된 props, state를 이용해야 함
- 이 때, props는 상위 컴포넌트에서 전달된 값이고, state는 현재 컴포넌트의 state 값임
- getDerivedStateFromProps() 함수는 상위 컴포넌트에서 전달받은 프로퍼티로 state 값을 연동할 때 주로 사용됨
- 반환값으로 state를 변경함
- componentDidMount() 함수 :
- componentDidMount() 함수는 render() 함수가 JSX를 화면에 그린 후에 호출됨
- 컴포넌트가 화면에 모두 표현된 이후 해야 하는 작업들은 이 함수에서 하면 됨
- shouldComponentUpdate(nextProps, nextState) 함수 :
- shouldComponentUpdate() 함수는 프로퍼티를 변경하거나 setState() 함수를 호출하여 state 값을 변경하면 화면을 새로 출력해야 하는지 판단함
- 화면을 새로 출력할지 말지 판단하고, 데이터 변화를 비교하는 작업을 포함하기 때문에 리액트 성능에 영향을 많이 줌
- 화면 변경을 위해 검증 작업을 해야 하는 경우 이 함수를 사용함
- forceUpdate() 함수를 호출하여 화면을 출력하면 이 함수는 출력되지 않음
- getSnapshotBeforeUpdate(prevProps, prevState) 함수 :
- getSnapshotBeforeUpdate() 함수는 컴포넌트의 변경된 내용이 가상 화면에 완성된 이후 호출됨
- 컴포넌트가 화면에 실제로 출력되기 전에 호출됨
- 따라서, 화면에 출력될 엘리먼트의 크기 또는 스크롤 위치 등의 DOM 정보에 접근할 때 사용됨
- componentDidUpdate(prevProps, snapshot) 함수 :
- componentDidUpdate() 함수는 컴포넌트가 실제 화면에 출력된 이후 호출됨
- 부모 컴포넌트로부터 전달된 이전 프로퍼티(=prevProps)와 이전 state 값(=prevState)과 함께 getSnapshotBeforeUpdate() 함수에서 반환된 값(=snapshot)을 인자로 전달받음
- 이 값들을 이용하여 스크롤 위치를 옮기거나 커서를 이동시키는 등의 DOM 정보를 변경할 때 사용됨
- componentWillUnmount() 함수 :
- componentWillUnmount() 함수는 컴포넌트가 소멸되기 직전에 호출됨
- 보통 컴포넌트에서 감시하고 있는 작업들을 해제할 때 필요한 함수
ex) 컴포넌트에 setInterval() 함수가 사용되었다면 clearInterval() 함수로 해제해야 함 - 해제 작업이 생략되면 메모리 누수 현상이 발생함
- 생성 과정에서 생명주기 함수의 실행 과정 (/src/index.js) :
└ index.js 파일의 ReactDom.render() 함수가 실행 되면import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import * as serviceWorker from './serviceWorker'; ReactDOM.render(<App />, document.getElementById('root')); serviceWorker.unregister();
App 컴포넌트의 최초 생명주기 함수인 constructor()가 실행됨
└ 이 때, App 컴포넌트는 자식 컴포넌트를 가지고 있음
자식 컴포넌트 또한 독립적인 생명주기를 가지고 있음
- 생성 과정의 자식 컴포넌트에서의 생명주기 함수의 실행 순서 (src/ex/LifeCycle.jsx) :
└ ⓐ : getDerivedStateFromProps() 함수를 사용하므로 경고 메시지를 건너 뛰기 위해 state 초기값을 사용함import React from 'react'; class LifeCycle extends React.Component { static getDerivedStateFromProps() { console.log('getDerivedStateFromProps() 함수 호출'); return {}; } constructor(props) { super(props); this.state = {}; // ⓐ console.log('constructor() 함수 호출'); } componentDidMount() { console.log('componentDidMount() 함수 호출'); } componentDidUpdate() { console.log('componentDidUpdate() 함수 호출'); } componentWillUnmount() { console.log('componentWillUnmount() 함수 호출'); } getSnapshotBeforeUpdate() { console.log('getSnapshotBeforeUpdate() 함수 호출'); return {}; } shouldComponentUpdate() { console.log('shouldComponentUpdate() 함수 호출'); return true; } render() { console.log('render() 함수 호출'); return null; } } export default LifeCycle;
- App 컴포넌트 수정 (/src/App.js) :
import React from 'react'; import LifeCycle from './ex/LifeCycle'; class App extends React.Component { render() { return ( <div> <LifeCycle /> </div> ); } } export default App;
- 결과 :
- 변경 과정에서 생명주기 함수의 실행 순서 (/src/ex/LifeCycle2.jsx) :
import React from 'react'; class LifeCycle2 extends React.Component { static getDerivedStateFromProps() { console.log('getDerivedStateFromProps() 함수 호출'); return {}; } constructor(props) { super(props); this.state = {}; console.log('constructor() 함수 호출'); } componentDidMount() { console.log('componentDidMount() 함수 호출'); this.setState({ updated: true }); } componentDidUpdate() { console.log('componentDidUpdate() 함수 호출'); } componentWillUnmount() { console.log('componentWillUnmount() 함수 호출'); } getSnapshotBeforeUpdate() { console.log('getSnapshotBeforeUpdate() 함수 호출'); return {}; } shouldComponentUpdate() { console.log('shouldComponentUpdate() 함수 호출'); return true; } render() { console.log('render() 함수 호출'); return null; } } export default LifeCycle2;
- App 컴포넌트 수정 (src/App.js) :
import React from 'react'; import LifeCycle2 from './ex/LifeCycle2'; class App extends React.Component { render() { return ( <div> <LifeCycle2 /> </div> ); } } export default App;
- 결과 :
- shouldComponentUpdate() 함수의 반환값이 false일 때 생명주기 함수의 실행 순서 (/src/ex/LifeCycle3.jsx) :
import React from 'react'; class LifeCycle3 extends React.Component { static getDerivedStateFromProps() { console.log('getDerivedStateFromProps() 함수 호출'); return {}; } constructor(props) { super(props); this.state = {}; console.log('constructor() 함수 호출'); } componentDidMount() { console.log('componentDidMount() 함수 호출'); this.setState({ updated: true }); } componentDidUpdate() { console.log('componentDidUpdate() 함수 호출'); } componentWillUnmount() { console.log('componentWillUnmount() 함수 호출'); } getSnapshotBeforeUpdate() { console.log('getSnapshotBeforeUpdate() 함수 호출'); return {}; } shouldComponentUpdate() { console.log('shouldComponentUpdate() 함수 호출'); return false; } render() { console.log('render() 함수 호출'); return null; } } export default LifeCycle3;
- App 컴포넌트 수정 (/src/App.js) :
import React from 'react'; import LifeCycle3 from './ex/LifeCycle3'; class App extends React.Component { render() { return ( <div> <LifeCycle3 /> </div> ); } } export default App;
- 결과 :
└ shouldComponentUpdate() 함수의 반환값이 false이므로 리액트 엔진은 데이터 비교 후 변경 사항이 없다고 판단하므로 이후의 변경 과정의 생명주기 함수가 실행되지 않음
- shouldComponentUpdate() 함수의 반환값이 false이고 componentDidMount() 함수의 setState() 함수를 forceUpdate() 함수로 변경했을 때 생명주기 함수의 실행 순서 (/src/ex/LifeCycle4.jsx) :
import React from 'react'; class LifeCycle4 extends React.Component { static getDerivedStateFromProps() { console.log('getDerivedStateFromProps() 함수 호출'); return {}; } constructor(props) { super(props); this.state = {}; console.log('constructor() 함수 호출'); } componentDidMount() { console.log('componentDidMount() 함수 호출'); this.forceUpdate(); } componentDidUpdate() { console.log('componentDidUpdate() 함수 호출'); } componentWillUnmount() { console.log('componentWillUnmount() 함수 호출'); } getSnapshotBeforeUpdate() { console.log('getSnapshotBeforeUpdate() 함수 호출'); return {}; } shouldComponentUpdate() { console.log('shouldComponentUpdate() 함수 호출'); return false; } render() { console.log('render() 함수 호출'); return null; } } export default LifeCycle4;
- App 컴포넌트 수정 (/src/App.js) :
import React from 'react'; import LifeCycle4 from './ex/LifeCycle4'; class App extends React.Component { render() { return ( <div> <LifeCycle4 /> </div> ); } } export default App;
- 결과 :
└ shouldComponentUpdate() 함수의 반환값이 false이지만 변경 과정의 생명주기 함수가 모두 실행되었음
- 소멸 과정에서 생명주기 함수의 실행 순서 (/src/App.js) :
import React from 'react'; import LifeCycle from './ex/LifeCycle'; class App extends React.Component { constructor(props) { super(props); this.state = { hasDestroyed: false }; } componentDidMount() { this.setState({ hasDestroyed: true }); } render() { return ( <div> <div> {this.state.hasDestroyed? null : <LifeCycle />} </div> </div> ); } } export default App;
- 결과 :
└ App 컴포넌트에서 LifeCycle 컴포넌트를 그리지 않도록 코드를 작성함
생명주기 함수를 사용하여 카운터 프로그램 만들기
- Counter 컴포넌트 (/src/ex/Counter.jsx) :
import React from 'react'; class Counter extends React.Component { constructor(props) { super(props); this.state = { count: props.count }; this.increase = this.increase.bind(this); } increase() { this.setState(({ count }) => ({ count: count + 1 })); } render() { return ( <div> 현재 카운트: {this.state.count} <button onClick={this.increase} > 카운트 증가 </button> </div> ); } } export default Counter;
- NewCounter 컴포넌트 (/src/ex/NewCounter.jsx) :
└ App 컴포넌트가 전달한 최초의 프로퍼티 값은 state.count에 저장됨import React from 'react'; class NewCounter extends React.Component { constructor(props) { super(props); this.state = {}; this.increase = this.increase.bind(this); } static getDerivedStateFromProps(props, state) { const { count } = props; return { count, newCount: count === state.count ? state.newCount //ⓐ : count // ⓑ }; } increase() { this.setState(({ newCount }) => ({ newCount: newCount + 1 })); } render() { return ( <div> 현재 카운트: {this.state.newCount} <button onClick={this.increase}> 카운트 증가 </button> </div> ); } } export default NewCounter;
└ NewCounter 컴포넌트는 state.newCount로 증가값을 따로 분리하여 관리함
└ getDerivedStateFromProps() 함수는 다른 프로퍼티가 변경되어도 호출되기 때문에
state.newCount로 증가값을 관리함
└ ⓐ : 프로퍼티가 변경되지 않았으면 기존 state값으로 설정
└ ⓑ : 프로퍼티가 변경되었으면 변경된 프로퍼티 값으로 설정
- App 컴포넌트 수정 (/src/App.js) :
import React from 'react'; import Counter from './ex/Counter'; import NewCounter from './ex/NewCounter'; class App extends React.Component { constructor(props) { super(props); this.state = { count: 5 }; this.reset = this.reset.bind(this); } reset() { this.setState(({ count }) => ({ count: count + 5})); } render() { return ( <div> <div><Counter count={this.state.count} /></div> <div><NewCounter count={this.state.count} /></div> <button onClick={this.reset}>{this.state.count + 5}로 초기화</button> </div> ); } } export default App;
- 결과 :
- 위아래의 카운트 증가 버튼을 한번씩 눌렀을 때의 결과 :
- 10로 초기화 버튼을 눌렀을 때의 결과 :
└ NewCounter 컴포넌트만 getDerivedStateFromProps() 함수로 App 컴포넌트부터 갱신된 프로퍼티 값을
동기화 했기 때문에 NewCounter 컴포넌트의 숫자만 초기화가 됨
└ Counter 컴포넌트는 처음 생성될 때만 프로퍼티값으로 state값을 설정하므로갱신 과정에서는 state 값이 변경되지 않음
'React > Component' 카테고리의 다른 글
컴포넌트 (6) - 콜백 함수, 이벤트 처리 (0) | 2021.09.11 |
---|---|
컴포넌트 (5) - 클래스형, 함수형, 배열 (0) | 2021.09.10 |
컴포넌트(3) - 상태 관리 (0) | 2021.09.07 |
컴포넌트(2) - 프로퍼티 (0) | 2021.09.07 |
컴포넌트 (1) - JSX, 컴포넌트 (0) | 2021.08.31 |