에 대해 알아보자
리액트 컴포넌트가 어떻게 돌아가는지 확인하기 앞서 컴포넌트가 무엇이고 어떤게 있는지 알아보자
In a React application, the basic structure item is a component.
Developers use them to split the code and isolate parts of the application.
데이터를 입력받아 Dom Node를 출력하는 일종의 함수
독립적인 UI
조각들을 만들고 재사용하며 컴포넌트들 끼리 유기적으로 연결되어 애플리케이션으로 동작한다.
리액트에서 컴포넌트 형태를 두 가지로 구분할 수 있다.
클래스형 컴포넌트 : 기본적으로 컴포넌트 수명주기와 상태관리를 할 수 있다.
함수형 컴포넌트 : 과거에는 state와 라이프사이클 관리가 안되었다. (react hook이 나오고 가능해짐)
리액트 16.8에서 hook
이 도입되며 상태와 라이프사이클 관리가 함수형 컴포넌트에서 가능해짐에 따라
클래스형보다는 hook
을 곁들인 함수형 컴포넌트로의 구성이 권장된다.
기존 클래스형 컴포넌트에서 어떤 라이프사이클 단계를 관리했는지 알아보자
우리 인생이랑 비슷하다.
Mount
DOM 객체가 생성되고 브라우저에 나타나는 것을 의미한다. (컴포넌트의 탄생)
Update
상태변화로 인해 나타나던 것이 변경되는 것(컴포넌트의 성장/변화)
Unmount
컴포넌트가 더 이상 사용되지 않아 DOM에서 제거되는 것(컴포넌트의 죽음)
생명주기는 세 가지가 끝이며
클래스형 컴포넌트에서는 각 라이프사이클을 처리하기 위한 내장 동작이 포함되어있었다.
컴포넌트가 DOM tree에 올려지는 것
(1) Constructor
해당 컴포넌트가 만들어질 때 처음으로 호출됨, state 초기값을 지정한다.
(2) componentWillMount()
render
메소드 직전에 호출된다.
17버전 이후로 deprecated 되었으며 getDerivedStateFromProps
로 대체되었다.
(3)getDerivedStateFromProps
렌더링 직전에 호출되며 초기props
을 기반으로state
값을 동기화하여 설정하는 함수
(4)render()
데이터가 변경되어 새 화면을 그려야 할 때 호출(상태 변화마다 호출됨)
컴포넌트의 필수 메소드이며 컴포넌트 마운트 시 렌더링되어야 하는 jsx
를 포함한다.
(5)componentDidMount()
컴포넌트를 생성하고 첫 렌더링이 끝나 DOM에 반영되고 호출되는 함수
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
show: true
};
console.log(`${props.name} constructor`);
}
static getDerivedStateFromProps(props, state) {
console.log(`${props.name} getDerivedStateFromProps`);
return null;
}
componentDidMount() {
console.log(`${this.props.name} componentDidMount`);
}
render() {
console.log(`${this.props.name} render`);
return (
<div>
{this.props.name}
{this.state.show && this.props.children}
</div>
);
}
}
export default function App() {
return (
<div className="App">
<h1>Hello LifeCycle</h1>
<br />
<MyComponent name="parent">
<MyComponent name="child" />
</MyComponent>
</div>
);
}
자 이제 componentDidMount
까지 와서 렌더링이 모두 되었고
상태가 변경되어 업데이트 되는 상황에서의 라이프사이클을 알아보자
(1)getDerivedStateFromProps
(2)shouldComponentUpdate(nextProps, nextState)
컴포넌트를 리렌더링 할지 말지 결정해준다.
기본적으로 컴포넌트는 prop
state
변경에 모두 리렌더링 되는데
리렌더링의 조건을 결정해준다.
(3)render()
(4)getSnapshotBeforeUpdate(prevProps, prevState)
변경 요소에 대해 DOM 객체에 반영하기 직전에 호출됨
render
메서드를 사용하여 다시 렌더링되기 직전에 호출된다.
componentDidUpdate
메소드에 3번째 파라미터로 전달됨
(5)componentDidUpdate(prevProps, prevState, snapshot)
DOM 트리에서 작동하거나 전달된 인수가 변경될 때의 처리를 한다.
컴포넌트가 DOM 에서 제거되는 것을 말함
더는 컴포넌트를 사용하지 않을 때 발생하는 이벤트가 있다.
componentWillUnmount()
이 메서드는 컴포넌트 소멸 직전에 호출된다.
clean-up
, 이벤트리스너, 구독 제거, 보류 중인 요청 중단 등에 사용된다.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
show: true
};
console.log(`${props.name} constructor`);
}
static getDerivedStateFromProps(props, state) {
console.log(`${props.name} getDerivedStateFromProps`);
return null;
}
componentDidMount() {
console.log(`${this.props.name} componentDidMount`);
console.log("====마운트 -완-====");
}
shouldComponentUpdate(nextProps, nextState) {
console.log(`${this.props.name} shouldComponentUpdate`);
return true;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log(`${this.props.name} getSnapshotBeforeUpdate`);
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(`${this.props.name} componentDidUpdate`);
}
componentWillUnmount() {
console.log(`${this.props.name} componentWillUnmount`);
}
showHide = () => {
this.setState({
show: !this.state.show
});
};
render() {
console.log(`${this.props.name} render`);
return (
<div>
{this.props.name}
<button onClick={this.showHide}>Toggle Show/Hide</button>
{this.state.show && this.props.children}
</div>
);
}
}
export default function App() {
return (
<div className="App">
<h1>Hello LifeCycle</h1>
<br />
<MyComponent name="parent">
<MyComponent name="child" />
</MyComponent>
</div>
);
}
child
컴포넌트를 제거하는 state
의 변경이 일어날 때
클래스 컴포넌트 생명주기 CodeSandbox
Diagram
상태와 사이클 관리가 클래스형 컴포넌트만 가능했었지만
hook
이 도입된 이후에는 함수형도 가능해지게 되었고 클래스형 컴포넌트로 구성을 잘 하지 않는다.
Hook
함수형 컴포넌트에서 클래스형 컴포넌트의 상태관리와 라이프사이클 관리를 가능하게 해주는 도구
단순화
우리 리액트 창조주님들 께서는 함수형에서도 상태를 관리할 수 있게 하고 기존 클래스형의 라이프사이클을 간소화 하고 쉽게 관리하도록 훅을 하사하셨다.
예를 들어 useState
훅은 컴포넌트에서 상태를 가지고 관리를 할 수 있게 해주었고
componentDidMount
, componentDidUpdate
, componentWillUnmount
는
useEffect
라는 하나의 hook
으로 관리할 수 있게 되었다.
예를 들면 다음과 같은 클래스형 컴포넌트에서의 관리 코드는
componentDidMount() {
this.subscription = props.data.subscribe(this.handleDataChange);
}
componentDidUpdate(prevProps) {
if(prevProps.data !== props.data) {
this.subscription.unsubscribe();
this.subscription - props.data.subscribe(this.handleDataChange);
}
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
함수형에서 다음과 같다.
useEffect(() => {
// componentDidMount
// componentDidUpdate with dependency
const subscription = props.data.subscribe(handleDataChange);
return () => {
// componentWillUnmount
// dependency에 따라 update시에도 수행
subscription.unsubscribe(handleDataChange)
}
//dependencies
}, [props.data])
useEffect
는 렌더링 이후 수행되어
componentDidMount
역할을 하면서도
의존성에 따라 componentDidUpdate
처리도 가능하고
return
으로 클린업 기능이 제공되어 컴포넌트 소멸 시의 사이드이펙트 제어가 가능한
componentWillUnmount
처리도 가능한 것이다.
결국 기존의 필요했던 상태 관리와 라이프 사이클 관리를 이론적으로는 저 두가지 hook들로 처리가 가능해졌다.
구성 요소에서 useEffect Hook을 사용할 때 구성 요소가 페이지에서 제거되면 더 이상 필요하지 않은 장기 실행 코드가 생길 수 있습니다.
예를 들면 이벤트, timeinterval 같은 것들
index.js:1 Warning: Can't perform a React state update on an unmounted
component. This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup
function.
안하면 이렇게 친절하게 알려준다고 한다.
예시
스스로의 이해를 돕고자 코센박으로 예제를 만들어보았다.
setTimeout
으로 시계 컴포넌트가 활성화 되었을 때 시간초가 흘러가도록 의도되었고
child 컴포넌트가 있을 경우 텍스트에 마우스를 가져다대면 콘솔이 찍히게끔 구현했다.
하지만 콘솔을 확인해보면 해당 컴포넌트들을 unmount
시켜도 시간이 계속 흘러가고 있고
텍스트에 마우스를 가져다대도 콘솔이 찍히는 것을 알 수 있다.
여전히 백그라운드에서 해당 동작들이 수행되고 있는 것이다
메모리 누수나 비정상적인 동작을 유발할 수 있기 때문에 클린업 처리를 해줘야 한다.
즉 componentWillUnmount
에서 컴포넌트가 소멸되기 전 처리를 해주는 것과 같다고 할 수 있다.
React의 솔루션은 우리의 useEffect 함수가 clean-up 함수를 반환하도록 하는 것입니다. 이 함수는 구성 요소가 마운트 해제될 때, 즉 부모가 더 이상 반환하지 않을 때 실행됩니다.
클린업 적용
클린업이 언제 수행될까??
render -> useEffect -> setState -> re-render -> cleanup -> (optional) useEffect
언제 해야되나
타이머, 이벤트, 웹 소켓 연결같은 stateful에서의 구독해제
의도적으로 clean-up
함수를 항상 작성할 필요는 없지만 언제 해야 유용할지는 알고 있을 것!