React State and Life Cycle (리액트 state와 생명주기)
1. State
리액트는 뷰의 단위가 컴포넌트로 구성되고, 이 컴포넌트를 통해 UI 를 재사용이 가능한 여러 조각으로 나눈다. 리액트로 프로젝트를 만들 때 우리가 주목해야 하는 부분은 props와 state다.
각 컴포넌트는 상위에서 하위 컴포넌트로 값을 전달하여 사용할 수 있는데, 이 때 이 전달되는 값을 props라고 한다.
state는 각 컴포넌트가 가지고 있는 개별적인 상태값이다.
props와 state는 일반 JS객체로, 두객체 모두 랜더링 결과물에 영향을 주는 정보를 갖고 있다.
그러나 한가지 중요한 방식에서 차이가 있는데, 변경 가능한 점에 대한 부분이다.
props는 부모 구성요소에서 설정한 정보를 포함하며 변경할 수 없는 불변성의 특징이 있다.
state는 구성요소가 자체적으로 초기화, 변경 및 사용할수 있는 정보를 포함하고, setState를 통해 변경이 가능하다.
즉, props는 함수의 매개변수처럼 컴포넌트에 전달되어 불변하고, state는 함수 내에 선언된 변수처럼 컴포넌트 안에서 관리되어 변경 가능하다.
state로 view에 관계된것을 넣으면 되는데, 상태가 변화함으로써 달라질 view를 생각하여 만든다. view 와 직접적인 관계가 없는 것들은 state로 만들 이유가 없다. State 는 props와 유사하지만, 비공개이며(해당 컴포넌트 이외의 다른곳에서 사용할 수 없음을 말한다.) 해당 컴포넌트에 의해 완전히 제어된다.
2. 생명주기(Life Cycle)
생명주기는 리액트 컴포넌트는 탄생부터 죽음까지의 단계를 말한다. 이 컴포넌트의 생명주기는 여러지점에서 개발자가 작업을 할 수 있도록 메서드를 오버라이딩 할 수 있게 해준다.
이 생명주기는 Class Component에서만 해당한다. function 컴포넌트는 라이프사이클이 없고, 그처럼 동작하는것이 없다.
이 생명주기의 단계는 아래와 같다. 단, version 16.3 이하일 때 이다. 최근의 새로운 버전에서는 이름이 바뀌었다.
- 전체 생명주기 과정
Initialization (준비) ⇒ constructor
Mounting (탄생) ⇒ 컴포넌트를 그리기 직전 - 그리기(render) - 그린 직후
Updation (변경) ⇒ props 또는 state의 변경
Unmounting (죽음) ⇒ 죽기 직전
3. v16.3 이전의 Component Life Cycle
3.1 Component 생성 및 마운트 (16.3 버전 이전)
constructor ⇒ ComponentWillMount ⇒ render(최초 랜더) ⇒ ComponentDidMount
Component를 처음 만들면, 리액트가 특정 타이밍이 되었을 때 각각의 메서드를 호출시키는 동작을 해준다.
- constructor : 생성자 api로 컴포넌트가 마운트 되기 전에 실행된다.
- componentWillMount : 마운트 되기 전에 호출되는 메서드다.
- componentDidMount : 컴포넌트가 render된 직후 바로 실행된다. 이 시점에 타이머, api 호출(데이터 요청), 스크롤 설정 및 최초 렌더시에만 수행하는 작업 등을 넣어주어야 한다.
위처럼 클래스를 선언을 해놓으면 이 선언을 실행하는 것은 리액트다. this를 밑에 코드에서 사용할 수 있는 이유는 react가 new를 사용하여 인스턴스를 내부적으로 만들었기 때문이다. 컴포넌트를 생성하고 마운트 되는 것을 확인 할 수 있는 코드는 다음과 같다.
class App extends React.Component {
_interval;
constructor(props) {
console.log('App constructor');
super(props);
this.state = {
age: 37,
};
}
componentWillMount() {
console.log('App componentWillMount');
}
componentDidMount() {
console.log('App componentDidMount');
this._interval = window.setInterval(() => {
this.setState({
age: this.state.age + 1,
});
// 1. 타이머 2. API를 호출 3. 렌더된 결과물에 작업하기 (최초에만 하는일)
}, 1000);
}
componentWillUnmount() {
console.log('App componentWillUnmount');
clearInterval(this._interval);
}
render() {
console.log('App render');
return (
<div>
<h2>
Hello {this.props.name} - {this.state.age}
</h2>
</div>
);
}
}
3.2 Component props 와 state의 변경 (v16.3 이전)
props 업데이트
: componentWillReceiveProps → shouldComponentUpdate → componentWillUpdate → render → componentDidUpdate
State 업데이트 : shouldComponentUpdate → componentWillUpdate → render → componentDidUpdate
1. componentWillReceiveProps
: props가 새로 지정되어 변경이 일어나면 바로 호출되는 메서드로 . (nextProps ⇒ state를 조작하는 공간)
state의 변경에 반응하지 않는다. state를 변경해야 한다면 setState를 이용해 변경한다.
2. shouldComponentUpdate (nextProps, nextState)
: props 또는 state가 변경되었을 때, re-rendering의 여부를 결정하는 메소드다. return 값을 기본으로 하여 이 여부를 결정한다.
리턴값이 true면 render 함수 호출하여 리렌더링을 한다. 그러나 리턴값이 false 면 리렌더링을 하지 않는다.
if 문등을 사용하여 this.props와 nextProps를 비교하고, 이 props 객체가 변했을때 새로운 객체를 만들어 랜더링을 다시한다.
이러한 특징이 컴포넌트의 필요한 업데이트만 할수 있게 함으로써 성능을 최적화한다.
그러나 이 메서드의 문제점이 있는데, 자식 컴포넌트에서는 변한 부분이 없는데도 부모 컴포넌트와 함게 리렌더링 된다는 점이다. 이러한 점은 자식 컴포넌트가 많아지면 많아질수록 자원낭비를 초래하게 된다.
3. componentWillUpdate (nextProps, nextState)
: 업데이트 하기 직전을 가리키고 컴포넌트가 렌더링이 다시 되기 직전에 호출되는 메서드다. 처음 렌더링시에는 호출되지 않는다.
여기서 setState을 쓰면 무한루프에 빠지게 되므로 사용하면 안된다.
4. render : 업데이트로 인해 다시 그리기
5. componentDidUpdate (prevProps, preState, snapahot)
: 업데이트 한 후를 가리키며 컴포넌트가 재 랜더링을 마치면 불리는 메서드다. 처음 마운트 때는 실행되지 않고, 현재 데이터와 이전 데이터를 비교하여 특정 로직을 만들 수 있다.
componentDidUpdate(prevProps) {
if (this.props.name !== prevProps.name) {
this.fetchDate(this.props.name);
}
}
3.3 Component 제거 (v16.3 이전)
View가 변경되어 기존의 컴포넌트들이 제거될 때에는 component가 unmount 된다고 말한다.
컴포넌트가 언마운트 된 직후에 componentWillUnmount()가 호출된다. 이때 사용하지 않는 상태들을 모두 초기화 하는 cleanup 함수를 호출을 하는 등의 자원을 해제하는 정리작업을 한다.
4. v16.3 ~ Component Life Cycle
기존의 생명주기 단계의 이름들이 의미가 명확하게 바뀌었다.
componentWillReceiveProps⇒ getDerivedStateFromPropscomponentWillUpdate⇒ getSnapshotBeforeUpdatecomponentWillMount⇒ getDerivedStateFromProps
4.1 Component생성 및 마운트 (v16.3)
constructor ⇒ static getDerivedStateFromProps (클래스 매서드) ⇒ render(최초랜더) ⇒ componentDidMount
getDerivedStateFromProps 의 return이 state가 된다. (조건부로 만들수 있다.)
import React from 'react';
class App extends React.Component {
state = {
age: 0,
};
static getDerivedStateFromProps(nextProps, prevState) {
console.log(nextProps, prevState);
if (prevState.age !== nextProps.age) {
return { age: nextProps.age };
}
return null;
}
render() {
console.log('App render');
return <div>{this.state.age}</div>;
}
}
export default App;
4.2 Component props, state 변경 (v16.3)
component가 업데이트 되면,
props 변경일 경우: static getDerivedStateFromProps 부터 시작 / state 변경일 경우: shouldComponentUpdate 부터 시작
- getSnapshotBeforeUpdate 메서드는 DOM 변화가 일어나기 직전의 DOM 상태를 가져올 수 있다. snapshot을 return하며 이렇게 return 한 값은 componentDidUpdate의 3번째 인자로 가져올수 있다. 이를 통해 업데이트 된 DOM의 전과 후를 기억할수 있도록 한다.