-
[React.js] LifecycleFront-End(Web)/React - 프레임워크(React, Next) 2020. 11. 27. 17:14반응형
🤷 Lifecycle 이란?
리액트 컴포넌트는 생명주기(Lifecycle)를 가진다. (created, rendered, added to DOM, updated, removed)
Lifecycle은 크게 세 가지(Mounting, Updating, Unmounting)로 분류할 수 있으며, 아래 그림을 참고하자.
📒 Lifecycle Methods
컴포넌트에는 Lifecycle을 다루기 위한 단계별 메소드들이 존재한다. (v16.3 이전 버전)
- Mounting: 컴포넌트가 실행되고 DOM에 추가되는 과정이다. 컴포넌트가 실행되면 우선 context, defaultProps, satae를 결정하고, 'componentWillMount' 메소드를 호출한다. 이후 'render'로 DOM에 컴포넌트를 부착하면, 'componentDidMount' 메소드를 호출하면서 Mount 동작이 완료된다.
- Updating: props를 받거나 state가 변하면서 컴포넌트가 업데이트되는 과정이다. 우선 컴포넌트의 업데이트를 감지하면 'componentWillReceiveProps' 메소드가 호출되고, 'shouldComponentUpdate' 메소드에서 실행여부를 판단한다. 업데이트를 진행한다면 'componentWillUpdate' 메소드가 호출된 두 업데이트가 완료되면, render과 동시에 'componentDidUpdate' 메소드가 호출되면서 Update 동작이 완료된다. state가 변할 경우엔 'componentWillReceiveProps' 생략!
- Unmounting: 컴포넌트가 DOM에서 제거되는 과정이다. 삭제될 컴포넌트에 대해 'componentWillUnmount' 메소드를 호출한다. Unmount를 통해 이벤트리스너 등 컴포넌트와 연결된 요소들을 정리하며, 동작이 완료되면 따로 DidMount를 호출하진 않음.Unmount가 끝난 컴포넌트는 다시 Re-mount 되거나 하지 않는다.(Lifecycle 종료)
- Mounting 동작
state 공부때 활용한 기본적인 컴포넌트 Mount 구조(constructor() -> render())에서, Mount 이후 동작(componentDidMount())을 추가한 것이다.
import React from 'react'; import ReactDOM from 'react-dom'; class Clock extends React.Component { // 1. constructor() 메소드: 컴포넌트 Mount 시작 constructor(props) { super(props); this.state = { date: new Date() }; } // 2. render() 메소드: 컴포넌트 DOM 표현 render() { return <div>{this.state.date.toLocaleTimeString()}</div>; } // 3. componentDidMount() 메소드: Mount 완료 + 이후 동작(setInterval) componentDidMount() { const oneSecond = 1000; setInterval(() => { this.setState({ date: new Date() }); }, oneSecond); } } ReactDOM.render(<Clock />, document.getElementById('app'));
* 위 코드는 시계 컴포넌트로 기본적인 초세기를 해야하므로, 업데이트가 아닌 componentDidMount() 이후 바로 this.setState로 최신화하는 것이다.
- Unmounting 동작
컴포넌트를 제거(Unmount)할 때, 동작(위에선 setInterval)을 제거해주지 않으면 App 효율이 떨어지며 아래같은 메세지가 발생한다.
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component.
따라서, Clock 컴포넌트 안에 componentWillUnmount() 메소드를 추가해준다. 여기선, clearInterval(인터벌명)이 적합하다.
// 3. componentDidMount() 메소드: Mount후 동작이며, 인터벌 제거를 위해 this.intervalID로 명명 componentDidMount() { const oneSecond = 1000; this.intervalID = setInterval(() => { this.setState({ date: new Date() }); }, oneSecond); } // 4. componentWillUnmount() 메소드: 컴포넌트가 Unmount 된 뒤의 동작. clearInterval() componentWillUnmount() { clearInterval(this.intervalID) }
- Updating 동작
import React from 'react'; export class Clock extends React.Component { // 1. 생성자 메소드(함수) constructor(props) { super(props); this.state = { date: new Date() }; } // 3-2. 인터벌 코드중복을 없애기 위한 메소드화 startInterval(time) { this.intervalID = setInterval(() => { this.setState({ date: new Date() }); }, time); } // 2. 렌더링 메소드 (isPrecise 모드 토글기능 추가) render() { return ( <div> {this.props.isPrecise ? this.state.date.toISOString() : this.state.date.toLocaleTimeString()} </div> ); } // 3-1. 마운팅 후 메소드 componentDidMount() {this.startInterval(1000);} // 4. 업데이트(isPrecise 토글기능) 확인하여 인터벌 변경 ** componentDidUpdate(prevProps) { if (this.props.isPrecise === prevProps.isPrecise) { return; } else { clearInterval(this.intervalID); this.startInterval(100); } } // 5. 언마운팅 메소드(인터벌 제거) componentWillUnmount() { clearInterval(this.intervalID); } }
3-2로 만든 startInterval() 메소드 내, this.setState 부분에서 state를 계속 최신화(Update) 하고 있다.
또한, 토글링을 통해 props.isPrecise에 변화(Update)가 있는 경우, 인터벌을 바꿔주는 componentDidUpdate() 메소드를 추가했다.
📒 React v16.3 이후로 변경된 부분
- componentWillMount, componentWillReceiveProps, componentWillUpdate를 v17 부터 사용불가
- componentWillReceiveProps 대체 메서드 추가 getDerivedStateFromProps
- componentWillUpdate 대체 메서드 추가 getSnapshotBeforeUpdate
- componentDidCatch 컴포넌트 에러 핸들링 API 추가
- 변경이유
- 초기 렌더링을 제어하는 방법이 많아져서 혼란이 됨.
- 오류 처리 인터럽트 동작시에 메모리 누수 발생할 수 있음.
- React 커뮤니티에서도 가장 혼란을 야기하는 라이프 사이클
- getDerivedStateFromProps()
componentWillReceiveProps() 메소드를 대체. 컴포넌트가 인스턴스화(표현)된 후 새로운 props를 받을 경우 호출된다. 변경된 state는 this.state가 아닌 return으로 반환한다
// 변경 전: componentWillReceiveProps(next) componentWillReceiveProps(nextProps) { if (this.props.name !== nextProps.name) { this.setState({ name: nextProps.name }); } } // 변경 후: getDerivedStateFromProps(next, prev) static getDerivedStateFromProps(nextProps, prevState) { if (prevState.name !== nextProps.name) { return { name: nextProps.name }; } return null; }
- getSnapshotBeforeUpdate()
componentWillUpdate() 메소드를 대체. 업데이트 후 DOM 표현 직전에 호출된다. (자주 사용되진 않음, 렌더링 중 스크롤 유지 등)
// 변경 전: componentWillUpdate(props, state) componentWillUpdate(nextProps, nextState) { if (this.props.list.length < nextProps.list.length) { this.previousScrollOffset = this.listRef.scrollHeight - this.listRef.scrollTop; } } // 변경 후: getSnapshotBeforeUpdate(props, state) getSnapshotBeforeUpdate(prevProps, prevState) { if (prevProps.list.length < this.props.list.length) { return ( this.listRef.scrollHeight - this.listRef.scrollTop ); } return null; }
- componentDidCatch
컴포넌트 오류 개선을 위한 메소드. 에러가 발생할 때 state를 발생시키고 특정 조건을 표현한다. (자식 컴포넌트 에러만 감지가능!)
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(error, info) { // Display fallback UI this.setState({ hasError: true }); // You can also log the error to an error reporting service logErrorToMyService(error, info); } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1>; } return this.props.children; } }
출처: https://velog.io/@kyusung/ (kyusung 님의 velog)
컴포넌트의 Lifecycle 개념이 매우 중요하다고 알았고, 난해하진 않지만 각 단계에서 주로 쓰이는 메소드들을 숙지해야겠다.
또한, props를 받아 state 변경하는 과정 전체는 codecademy 예시에서 보지 못했는데 프로젝트를 진행하며 익혀가야겠다.
반응형'Front-End(Web) > React - 프레임워크(React, Next)' 카테고리의 다른 글
[React.js] Hooks - useEffect() (0) 2020.11.28 [React.js] Hooks - useState() (0) 2020.11.27 [React.js] State (0) 2020.11.26 [React.js] Props (0) 2020.11.26 [React.js] Components Interact, Import/Export (0) 2020.11.26