React의 컴포너트는 생명주기(Life cycle)을 가진다. 생명주기란 컴포넌트가 생성되고 사용되고 소멸될 때 까지 일련의 과정을 말한다.
이러한 생명주기 안에서는 특정 시점에 자동으로 호출되는 메서드가 있는데, 이를 라이프 사이클 이벤트라고 한다.
class Content extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
console.log('Component WILL MOUNT!');
}
componentDidMount() {
console.log('Component DID MOUNT!');
}
componentWillReceiveProps(nextProps) {
console.log('Component WILL RECIEVE PROPS!');
}
shouldComponentUpdate(nextProps, nextState) {
return true;
}
componentWillUpdate(nextProps, nextState) {
console.log('Component WILL UPDATE!');
}
componentDidUpdate(prevProps, prevState) {
console.log('Component DID UPDATE!');
}
componentWillUnmount() {
console.log('Component WILL UNMOUNT!');
}
render() {
return (
<div>
<h1>{this.props.sentDigit}</h1>
</div>
);
}
}
constructor(props){
super(props);
}
생성자 메소드로 컴포넌트가 생성될 때 단 한번만 실행된다.
이 메소드에서만 state
, context
, defaultProps
를 설정할 수 있다. (JS Class 참고)
React
엘리먼트를 실제 DOM 노드에 추가하기 직전에 호출되는 메소드다.
DOM
이 생성되지 않았으므로 DOM
을 조작할 수 없고, render
가 호출되기 전이기 때문에 setState
를 사용해도 render
가 호출하지 않는다.
componentWillMount
에서는props
나state
를 바꾸면 안 된다.Mount
중이기 때문이다. 아직DOM
에render
하지 않았기 때문에DOM
에도 접근할 수 없다.
컴포넌트 렌더링을 담당한다.
컴포넌트가 만들어지고 render
가 호출된 이후에 호출되는 메소드다.
componentDidMount에서는 DOM에 접근할 수 있다. AJAX
나 setTimeout
, setInterval
등 타이머를 생성하는 코드를 작성하는 부분이다.
컴포넌트 생성후에 첫 렌더링을 마친 후 호출되는 메서드다.
컴포넌트가 처음 마운트 되는 시점에서는 호출되지 않는다.
props
를 받아서 state
를 변경해야 하는 경우 유용하다.
이 메소드 내부에서 setState
를 사용해도 추가적인 렌더링이 발생하지 않는다.
shouldComponentUpdate(nextProps, nextState) {
// return false 하면 업데이트를 안함
// return this.props.checked !== nextProps.checked
return true;
}
컴포넌트 업데이트 직전에 호출되는 메소드다.
props
또는 state가 변경되었을 때, 재랜더링을 여부를 return 값으로 결정한다.
이 API
는 컴포넌트를 최적화하는 작업에서 매우 유용하게 사용된다. 리액트에서는 변화가 발생하는 부분만 업데이트를 해줘서 성능이 잘나온다. 하지만, 변화가 발생한 부분만 감지해내기 위해서는 Virtual DOM 에 한번 그려줘야한다.
즉, 현재 컴포넌트의 상태가 업데이트되지 않아도, 부모 컴포넌트가 리렌더링되면, 자식 컴포넌트들도 렌더링 된다. 여기서 “렌더링” 된다는건, render()
함수가 호출된다는 의미.
변화가 없으면 물론 DOM
조작은 하지 않게 된다. 그저 Virutal DOM
에만 렌더링 할 뿐이다. 이 작업은 그렇게 부하가 많은 작업은 아니지만, 컴포넌트가 무수히 많이 렌더링된다면 얘기가 조금 달라진다. CPU
자원을 어느정도 사용하고 있는것은 사실이기 때문에.
쓸대없이 낭비되고 있는 이 CPU
처리량을 줄여주기 위해서 우리는 Virtual DOM
에 리렌더링 하는것도,불필요할경우엔 방지하기 위해서 shouldComponentUpdate
를 작성한다.
이 함수는 기본적으로
true
를 반환한다. 우리가 따로 작성을 해주어서 조건에 따라false
를 반환하면 해당 조건에는render
함수를 호출하지 않는다. 주로 여기서 성능 최적화를 한다
shouldComponentUpdate
가 불린 이후에 컴포넌트 업데이트 직전에서 호출되는 메소드다.
새로운 props
또는 state
가 반영되기 직전 새로운 값들을 받는다.
이 메서드 안에서 this.setState()
를 사용하면 무한 루프가 일어나게 되므로 사용하면 안된다. 아직 props
도 업데이트하지 않았으므로 state
를 바꾸면 또 shouldComponentUpdate
가 발생한다.
다시 렌더링이 진행된다.
컴포넌트 업데이트 직후에 호출되는 메소드다.
컴포넌트가 소멸된 시점에(DOM에서 삭제된 후) 실행되는 메소드다.
컴포넌트 내부에서 타이머나 비동기 API를 사용하고 있을 때, 이를 제거하기에 유용하다.
componentWillMount
에서 주로 연결했던 이벤트 리스너를 제거하는 등의 여러 가지 정리 활동을 한다.
componentDidCatch(error, info) {
console.error(error, info);
}
위와 같이 사용하고, 최상위 컴포넌트에 한 번만 넣어주면 된다. 에러 발생 시 어떻게 대처할 것인지를 정의할 수 있습니다. 최소한 에러를 로깅하는 것만으로도 사용 가치가 있다.
import React from 'react';
import ReactDOM from 'react-dom';
import './styles.css';
const App = () => (
<div>
<Clock />
</div>
);
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date(),
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
componentWillMount
, componentWillReceiveProps
, componentWillUpdate
를 v17 부터 사용불가componentWillReceiveProps
대체 메서드 추가 getDerivedStateFromProps
componentWillUpdate
대체 메서드 추가 getSnapshotBeforeUpdate
componentDidCatch
컴포넌트 에러 핸들링 API 추가변경 이유
- 초기 렌더링을 제어하는 방법이 많아져서 혼란이 됨.
- 오류 처리 인터럽트 동작시에 메모리 누수 발생할 수 있음.
- React 커뮤니티에서도 가장 혼란을 야기하는 라이프 사이클
getDerivedStateFromProps
은 componentWillReceiveProps
의 대체 역할로 작성된 메서드로 컴포넌트가 인스턴스화 된 후, 새 props
를 받았을 때 호출된다. 주의할 점으로 setState
를 사용하는 것이 아닌 값을 return
해야한다. state
를 갱신하는 객체를 반환할 수 있고, 새로운 props
가 state
갱신을 필요로 하지 않음을 나타내기 위해 null
을 반환할 수도 있다.
// before (deprecated...)
componentWillReceiveProps(nextProps) {
if (this.props.name !== nextProps.name) {
this.setState({ name: nextProps.name });
}
}
// updated (after v16.3)
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.name !== nextProps.name) {
return { name: nextProps.name };
}
return null;
}
getSnapshotBeforeUpdate
은 componentWillUpdate
의 대체 역할로 작성된 메서드로 DOM
이 업데이트 직전에 호출된다. (이 라이프 사이클은 많이 필요하지 않지만, 렌더링되는 동안 수동으로 스크롤 위치를 유지해야할 때와 같은 경우에는 유용할 수 있다)
// before (deprecated...)
componentWillUpdate(nextProps, nextState) {
if (this.props.list.length < nextProps.list.length) {
this.previousScrollOffset =
this.listRef.scrollHeight - this.listRef.scrollTop;
}
}
// updated (after v16.3)
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
return (
this.listRef.scrollHeight - this.listRef.scrollTop
);
}
return null;
}
컴포넌트 오류 처리 개선을 위해 추가되었다.
에러 발생시에 state
를 변경하고 render
에서 해당 처리를 구현하면 된다.
주의할점은 자식 컴포넌트에서 발생하는 에러만 잡아낼 수 있고, 자신의 에러는 잡아낼수 없다.
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/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B5%90%EA%B3%BC%EC%84%9C-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4-%EC%9D%B4%EB%B2%A4%ED%8A%B8#:~:text=%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4%20%EC%9D%B4%EB%B2%A4%ED%8A%B8-,%EB%9D%BC%EC%9D%B4%ED%94%84%20%EC%82%AC%EC%9D%B4%ED%81%B4%20%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%9E%80,%EB%9D%BC%EC%9D%B4%ED%94%84%20%EC%82%AC%EC%9D%B4%ED%81%B4%20%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%9D%BC%EA%B3%A0%20%ED%95%9C%EB%8B%A4.
- https://www.zerocho.com/category/React/post/579b5ec26958781500ed9955