생명주기 메서드
모든 컴포넌트는 다양한 종류의 생명주기 메소드를 가집니다. 그리고 이 메소드를 오버라이딩하여 특정 시점에 코드가 실행되도록 설정할 수 있습니다.
import React from 'react';
import ReactDOM from 'react-dom';
import reportWebVitals from './reportWebVitals';
class Test extends React.Component{
constructor(props) {
super(props);
this.state = {
name: 'jiwon',
date: new Date()
}
}
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
// 기준간격을 두고 주기적으로 이벤트 발생
}
componentWillUnmount() {
clearInterval(this.timerID);
//기준 간격이 지정된 반복 작업을 중단함.
}
tick() {
this.setState({
date : new Date()
})
}
render() {
return (
<div>
{this.state.name}<br/>
{this.state.date.toLocaleTimeString()}
</div>
)
}
}
ReactDOM.render(
<Test />,
document.getElementById('root')
);
메서드의 호출 순서 요약
<Test />
가 ReactDOM.render()로 전달되었을 때 React는Test Component
의 constructor 호출. 그리고 this.state 초기화.- React는
Test
컴포넌트의 render() 메서드 호출(이를 통해 출력값을 인지함) 후Test
의 렌더링 출력값을 일치시키기 위해 DOM update. Test
출력값이 DOM에 삽입되면 React가 ComponentDidMount() 메서드 호출 -> 내부 코드 수행- 매초 브라우저가 tick() 메서드 호출. tick()에서
Test Component
는 setState에date : new Date()
를 호출하면서 UI업데이트를 진행합니다. - setState를 통해 React는 state가 변경된 것을 인지하고 화면에 표시될 내용을 알아내기위해 render() 메서드를 다시 호출합니다. 이 때 render() 메서드 안의 this.state.date가 달라지고 렌더링 출력 값은 업데이트 된 시각을 포함합니다.
- Test Component가 DOM으로부터 한 번이라도 삭제된 적이 있으면 React는 ComponentWillUnmount() 생명주기 메서드를 호출합니다.
심화학습
컴포넌트 생명주기(LifeCycle)
Render:
컴포넌트가 어떠한 사이드 이펙트 없이 렌더링 된다. 이는 Pure Component에 적용되며, 이 단계에서는 일시정지, 중단, 렌더 재시작등이 가능하다.
Pre-commit
: 컴포넌트가 실제 변화를 DOM에 반영하기 전에, 리액트가 DOM을 getSnapshotBeforeUpdate() 통해서 DOM 을 읽을 수도 있다.
Commit
: React는 DOM과 함께 작동하며, 각각의 라이프 사이클 마지막에 실행되는 것들이 포함된다. componentDidMount() componentDidUpdate() componentWillUnmount()
Mounting
컴포넌트의 인스턴스가 생성되어 DOM 상에 삽입될 때에 순서대로 호출
1. constructor()
리액트 컴포넌트의 생성자(constructor())은 컴포넌트가 마운트되기 전에 호출됩니다.
React.Component를 상속한 컴포넌트의 생성자를 구현할 때는 다른 구문에 앞서 super(props)를 호출해야합니다.
constructor() 내부에서는 setState를 사용하는 것이 아니라 this.state에 초기 값을 할당합니다. (생성자는 this.state를 직접할당할 수 있는 유일한 곳)
⚠️ 생성자 내에서는 side effect를 발생시키거나 subscription을 수행하면 안됩니다. 해당 경우엔 componentDidMount()를 대신 사용해야합니다.
⚠️ state에 props를 복사하면 안됩니다. props의 값이 바뀌어도 state에 반영되지 않습니다. 따라서 props의 갱신을 의도적으로 무시할 때 이런 패턴을 사용할 수 있습니다.
1-1. super() 안 쓰면 어떻게 되나?
class Test extends React.Component{
constructor(props) {
this.state = {
name: 'jiwon',
date: new Date()
}
}
...
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
super를 사용하지 않고 this를 사용하자 에러가 났습니다.
1-2. super()가 뭐지?
자바스크립트에서 super() 는 부모클래스 생성자의 참조입니다.
1-3. super() 왜 써야하나?
자바스크립트는 상속 클래스의 생성자 함수(constructor())와 그렇지 않은 생성자 함수를 구분합니다. 상속 클래스의 생성자 함수(constructor())에는 특수 내부 프로퍼티인 [[ConstructorKind]]:"derived"
가 이름표처럼 붙습니다.
(상속 클래스가 아니면 "base" 값을 가짐)
일반 클래스의 생성자 함수와 상속 클래스 생성자 함수간의 차이
- 일반 클래스가
new
와 함께 실행되면 빈 객체가 만들어지고 this에 이 객체를 할당. - 상속 클래스 생성자 함수는, 빈 객체를 만들고 this에 이 객체를 할당하는 일을 부모 클래스 생성자가 해주길 기대.
- 그리고 자식클래스의 인스턴스에는 자식클래스의 고유 멤버 뿐만 아니라 부모 클래스의 모든 멤버도 포함 돼있기 때문에 부모 클래스의 멤버초기화를 위해 자식클래스 생성자에서 super()로 호출하는 것입니다.
그래서 super()로 부모생성자 호출을 안하면 this가 될 객체가 만들어지지 않아 에러가 발생합니다.
1-4. 그럼 super에 props를 전달하지 않으면?
class Test2 extends React.Component {
constructor(props) {
super();
console.log(this.props); // undefined
}
render() {
return (
<div>
{this.props.testProps} {/* strs */}
</div>
)
}
}
props인자 없이 super()를 호출했는데 render나 다른 메서드에서도 this.props에 접근이 가능합니다.
이는 리액트가 생성자를 호출한 직후에 인스턴스에 props를 할당하는 것이기 때문입니다. 하지만 생성자가 끝나기 전에 this.props는 undefined가 될 것이기 때문에 super를 사용하는 것이 좋습니다.
static getDerivedStateFromProps()
getDerivedStateFromProps
는 최초 마운트 시와 갱신 시 모두에서 render
메서드를 호출하기 직전에 호출됩니다. (state 갱신을 위한 객체나, null을 반환)
이 메서드는 시간이 흐르는 것에 따라 변하는 props에 state가 의존하는 드문 사례를 위해 존재합니다.
주로 props로 받은 값을 state에 동기화시키고 싶을 때 사용됩니다. mount과정에서도 이 메서드가 실행되고 , update과정에서도 props가 바뀌면 실행됩니다.
render()
render()
메서드는 클래스 컴포넌트에서 반드시 구현돼야하는 유일한 메서드이고, 이 메서드가 호출되면 this.props와 this.state의 값을 활용하여 React 엘리먼트
, 배열
과 Fragment
, Portal
, 문자열과 숫자
, Boolean
또는 null
을 반환해야합니다.
shouldComponentUpdate()가 false를 반환하면 render()는 호출되지 않습니다.
render() 는 순수해야합니다. 컴포넌트의 state를 변경하지 않고 호출 마다 동일한 결과를 반환하고 브라우저와 직접적으로 상호작용하지 않습니다.
React DOM 및 refs 업데이트
componentDidMount()
componentDidMount()
는 컴포넌트가 마운트된 직후, 즉 트리에 삽입된 직후에 호출됩니다. DOM 노드가 있어야하는 초기화 작업은 이 메서드에서 이뤄지면 됩니다. (네트워크 요청에 적절한 위치입니다.)
Updating
컴포넌트가 re-rendering 될 때, 순서대로 호출되는 methods
static getDerivedStateFromProps()
shouldComponentUpdate()
shouldComponentUpdate(nextProps, nextState)
컴포넌트가 update되는 성능을 최적화 시키고싶을 때 사용합니다. 컴포넌트는 부모 컴포넌트가 리렌더링되면 자식컴포넌트들도 다 render()를 실행하게 됩니다. component는 실제로 update된 것만 바뀌기 때문에 렌더링 과정이 빠릅니다. 그러니까 render() 메서드는 호출이 다 되는데 Real-DOM 상에는 반영이 되지 않는 것입니다. 하지만 일단 render() 메서드가 Virtual-DOM 상에 그리긴 합니다. 따라서 컴포넌트가 많아서 Virtual-DOM상에 그리는 것도 아껴야할 때 사용합니다.
props
나 state
가 변경 되었을 때 리렌더링 여부를 return 값으로 반환합니다.
shouldComponentUpdate
를 사용하면 state 또는 props의 변화가 컴포넌트의 출력 결과에 영향을 미치는지 여부를 React가 알 수 있습니다. 기본 동작은 매 state마다 다시 렌더링을 수행하고, 대부분 경우 기본 동작을 따라야합니다.
이 메서드는 초기 렌더링 또는 forceUpdate()가 사용될 때는 호출되지 않습니다.
직접 작성 보다 PureComponent를 사용하는 것이 좋고 직접 작성할 때는 this.props와 nextProps, 그리고 this.state와 nextState를 비교한뒤 true, false를 반환할 수 있습니다.
false를 반환할 경우 render(), 그리고 componentDidUpdate()는 호출되지 않습니다.
class Test extends React.Component{
constructor(props) {
super(props);
this.state = {
name: 'jiwon',
}
}
componentDidUpdate() {
console.log("updated")
}
render() {
return (
<div>
{this.state.name}<br/>
<button onClick={()=>this.setState({name:'jiwon'})}>업데이트</button>
</div>
)
}
}
위의 코드는 업데이트를 누르면 리렌더링 되며 console.log가 찍힙니다.
여기에서 shouldComponentUpdate를 사용해보면
false가 반환되어 updated 는 로그에 찍히지 않는 것을 확인할 수 있습니다.
render
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)
렌더링 결과가 실제 DOM에 반영되기 전에 호출됩니다. 그리고 다음순서 메서드인 componentDidUpdate는 컴포넌트가 업데이트된 후에 호출됩니다. 이름처럼 업데이트 되기전 snapshot(props & states)를 확보하는게 목적이며, 컴포넌트가 DOM으로 부터 스크롤 위치등과 DOM의 크기 같은정보를 이후 변경되기 전에 얻을 수 있고, 이 메서드가 반환하는 값은 componentDidUpdate의 인자로 전달됩니다.
따라서 snapshot을 이 메서드에서 리턴하면 이후에 componentDidUpdate에서는 이전 속성, 이전 상태, snapsot을 이용해 돔의 상태값 변화를 알 수 있습니다.
componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate
는 업데이트가 일어난 직후에 호출됩니다. 최초 렌더링에는 호출되지 않습니다.
여기에서는 setState()를 호출할 수 있지만 조건문이 없다면 무한반복이 일어날 수 있습니다.
Unmounting
컴포넌트가 DOM 상에서 제거될 때에 호출되는 method
componentWillUnmount()
componentWillUnmount()
componentWillUnmount()는 마운트가 해제되어 제거되기 직전에 호출됩니다. 이 컴포넌트는 다시 렌더링 되지 않으므로 setState()를 호출해서는 안됩니다.
class Test extends React.Component{
state = {
show : false
}
componentDidMount() {
console.log('마운트 되었습니다.')
}
componentDidUpdate() {
console.log("업데이트 되었습니다.")
}
handleSomething = () => {
this.setState({show: !this.state.show});
}
render() {
return (
<div>
<button onClick={this.handleSomething}>버튼</button>
{!this.state.show && <Test2 />}
</div>
)
}
}
class Test2 extends React.Component {
componentWillUnmount() {
console.log('Test2가 언마운트 되었습니다.')
}
componentDidMount() {
console.log('Test2가 마운트 되었습니다.')
}
render() {
return <div>Test2 </div>
}
}
조건부 렌더링을 구현한 코드입니다.{!this.state.show && <Test2 />}
this.state.show를 기준으로 Test2
컴포넌트 출력 여부를 결정합니다.
버튼을 눌러 컴포넌트를 지우면
componentWillUnmount메소드가 호출되고, 실제 DOM상에서 해당 컴포넌트가 지워진 것을 확인할 수 있습니다.
setState()
setState()의 특성
setState 사용시 몇가지 특성을 기억해야합니다.
1. setState()는 비동기로 처리됨.
setState는 왜 비동기???
- react는 state와 props 값에 따라 re-rendering이 일어납니다. setState() 함수가 호출되면 리액트는 전달 받은 값으로 바로 state를 바꾸는 것이아니라, 리액트의 Element Tree와 전달 받은 state가 적용된 Element Tree를 비교하고 최종적으로 변경된 부분만 Real-DOM에 적용합니다.
- 그런데 setState() 가 동기적으로 실행된다면, setState()로 state가 변경될 때마다 새롭게 render가 되면서 비교하는 과정을 다시 거치는 알고리즘이 실행될 것입니다. 이는 비효율적이고 성능상으로도 불리할 수 있습니다.
이로써 지금까지 console.log에 state값이 한박자 느리게 찍혔던 이유를 알 수 있게 되었습니다.
class Test2 extends React.Component{
state = {
count: 0,
}
handleSomething = () => {
console.log('before', this.state.count);
this.setState({count: this.state.count + 1})
console.log('after', this.state.count);
}
render() {
return (
<>
<button onClick={this.handleSomething}>버튼</button>
<div>{this.state.count}</div>
</>
)
}
}
비동기
!!!
2. setState()를 연속적으로 호출하면 Batch 처리함.
- React는 batch update를 16ms 단위로 진행을 합니다. 즉 16ms 동안 변경된 상태 값들을 모아서 한번에 리랜더링을 진행한다는 것을 의미합니다. 이런 행동은 웹페이지 렌더링 횟수를 줄여줍니다.
(현재 batch update 는 setState reaction 이벤트 핸들러 내부의 업데이트만 기본적으로 해당 된다고 합니다.)
- 이런 상황에서 리액트는 setState를 세번이나 하는 대신 배치로 처리하게됩니다.
- 리액트는 setState()가 연속적으로 호출되면 전달받은 각각의 state를 합치는 작업을 수행한 뒤 한 번에 setState()를 합니다.
자세한건 여기서 https://usecode.pw/functional-set-state-is-the-future-of-react/
setState()의 구조
setState는 인자로 partialState와 callback을 받도록 돼있습니다. partialState는 Object와 Function을 인자로 받고 다음 partial state를 현재 state와 merge합니다.
뒤 따르는 callback은 function을 인자로 받고 인자로 넣어 실행하면 setState가 완료된 뒤에 실행이됩니다.
그래서 이런식으로 실행을 하면 partialState가 업데이트 되고 나서 console.log로 스테이트를 찍기 때문에 동기적으로 작동하는 것 처럼 보일 수 있습니다.
이 callback에도 setState를 넣을 수 있습니다.
이렇게 하면 한번에 +2씩 실행되는 것처럼 보이지만 사실은 렌더링이 2번 일어납니다.
componentDidUpdate() {
console.log("update")
}
그럼 어떻게????
위에서 확인했듯 partialState에 function도 받을 수 있도록 되어있습니다.
위의 코드를 function form 으로 변경해서 previousState를 인자로 받아 object를 return하는 function을 다시 setState의 인자로 받습니다.
이렇게 하면 한번의 update로 원하는 결과를 얻을 수 있습니다.
참고 및 출처
- https://ko.reactjs.org/docs/react-component.html
- https://han-py.tistory.com/183
- https://ko.javascript.info/class-inheritance
- https://www.npmjs.com/package/babel-plugin-transform-class-properties
- https://ui.toast.com/weekly-pick/ko_20170630#%ED%95%A8%EC%88%98-%EA%B0%9D%EC%B2%B4
- https://bumkeyy.gitbook.io/bumkeyy-code/frontend/a-deep-dive-into-react-fiber-internals
- https://leehwarang.github.io/2020/07/28/setState.html
- https://medium.com/react-native-seoul/react-%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%A5%BC-%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90-03-react-%EC%9D%98-reconciliation-%EA%B3%BC%EC%A0%95-2e6fb59c0c2d
- https://usecode.pw/functional-set-state-is-the-future-of-react/
- https://medium.com/@devych.code/react-setstate%EB%8A%94-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EB%8F%99%ED%95%9C%EB%8B%A4-b045737c9e41
댓글