● 클래스형 컴포넌트와 함수형 컴포넌트의 차이점
함수형 컴포넌트는 렌더링된 값들을 고정시킨다.
이 둘의 가장 큰 차이점은 props, state의 값들을 고정시키느냐 안시키느냐에 있습니다.
App.js
import { useState, useEffect } from 'react';
const App = ()=>{
const [number, setNumber] = useState(0)
useEffect(()=>{
setTimeout(()=>{ console.log({number})}, 3000)
})
return (
<div>
<h3>현재 숫자는 {number} 입니다.</h3>
<button onClick={()=>{setNumber(number + 1)}}>1씩 올리기</button>
</div>
)
}
export default App;
함수형 컴포넌트는 componentDidUpdate 메서드가 없기 때문에, 이를 대체할 수 있는 useEffect Hook을 사용해 버튼을 눌러 state가 업데이트 될 때마다 3초뒤 name의 값이 콘솔창에 뜨게 했습니다.
버튼을 세번 클릭하면 콘솔창에는 아래와 같이 뜨게 됩니다.
그렇다면 클래스형 컴포넌트는 이 똑같은 동작을 하면 어떻게 콘솔창에 뜨게 될까요?
App.js
import { Component } from 'react';
class App extends Component{
state = {
number : 0,
}
componentDidUpdate(){
setTimeout(()=>{
console.log(`${this.state.number}`)
}, 3000)
}
render(){
return(
<div>
<h3>현재 숫자는 {this.state.number} 입니다.</h3>
<button onClick={()=>{this.setState({number : this.state.number+1})}}>1씩 올리기</button>
</div>
)
}
}
export default App;
위는 클래스형 컴포넌트만든 컴포넌트입니다. 버튼을 3번 누르게 된다면?
1, 2, 3이 차례대로 출력되지 않고 3만 3번을 출력하는 것을 볼 수 있습니다.
따라서 함수형 컴포넌트는
- state가 업데이트 될 때마다 컴포넌트를 호출합니다.
- 매 랜더 결과물의 state와 props를 '보게' 됩니다.
- 각각 격리된 렌더링의 고유의 state와 props를 '보는' 것입니다.
쉽게 말하자면 함수형 컴포넌트는 사진처럼 각각의 렌더링의 state와 props를 기억하는 것입니다.
그럼 함수형 컴포넌트와 클래스형 컴포넌트는 왜 이런 결과가 나타날까요?
저는 이런 결과에 대해서 의문이 들었습니다.
- 클래스형 컴포넌트가 호출되었을 때 각각의 인스턴스를 만들지 않나? 그럼 큐에 들어가있던 setTimeout 함수는 콜스택에 왔을 때 this 바인딩을 잃어버리고 그냥 최근의 인스턴스를 가르키게 되는건가 ? (클래스의 프로퍼티 메서드는 무조건 생성된 인스턴스를 가르킨다고 했는데?)
- this가 달라질 수 있음에는 당연히 이해한다. 하지만 위의 컴포넌트에서 this는 무조건 인스턴스를 가리킬 수 밖에 없는데? 왜지? 그럼 매번 생성된 클래스형 컴포넌트에서는 리렌더링 되면 이전의 인스턴스를 버리나?
● 클래스형 컴포넌트는 인스턴스를 한번만 생성한다.
깔끔한 정답은 인스턴스를 한번만 생성한다는 것이었습니다.. !! this가 가르키는 곳은 변하지 않아요.. 그 인스턴스를 가르키는건 똑같지만 state가 변하기 때문이었습니다.
인스턴스를 한번만 생성하고, state가 변하면 기존 인스턴스의 state에 변화를 주게 되는 것입니다. 버튼을 3번 누르고 난뒤, componentDidUpdate안에 있는 setTimeout 비동기 함수가 실행될 때 가르키는 this는 당연히 3로 변해있으니, 3으로 출력되는 것입니다. 넘나리 당연한 말이었습니다!
chatGPT에게도 물어보았습니다.
- 클래스형 컴포넌트는 하나의 인스턴스만 생성한다.
- 클래스형 컴포넌트는 가장 최신값을 가르키게 된다.
아직 까지 감이 안오실 수도 있습니다. 하지만 함수형 컴포넌트를 보면 바로 이해할 수 있습니다.
● 함수형 컴포넌트는 렌더링될때마다 새로운 인스턴스를 생성한다.
함수형 컴포넌트는 렌더링 될 때마다 새로운 인스턴스를 생성합니다.
저는 useState를 쓸 때마다 드는 의문점이 있었습니다.
- const는 재할당, 재선언이 불가능한 '상수'인데 어떻게 setter함수로 값을 수정을 할 수 있는 것인가?
- 그냥 리액트 내부상 돌아가는 시스템임?
하지만 제 생각이 맞았습니다. const는 상수이기때문에 재선언, 재할당 할 수 없습니다.
그냥 함수는 새로운 인스턴스 만들때마다 인스턴스 내에서 새로 변수선언을 해주는 것이었습니다.
아까 App.js를 예시로 들어보자면 버튼을 3번 누른다면
처음
const App = ()=>{
const number = 0;
...
}
첫번째 눌렀을 때
const App = ()=>{
const number = 1;
...
}
두번째 눌렀을 때
const App = ()=>{
const number = 2;
...
}
세번째 눌렀을 때
const App = ()=>{
const number = 0;
...
}
이런식으로 새로운 인스턴스를 만들어서 내부에서 선언해주고 있던 것이었습니다. 이렇게 된다면 고유한 state와 props이 보장되게 됩니다. 그림으로 표현하면 아래와 같겠습니다.
또한 추가적으로 이야기하자면, 함수형 컴포넌트는 렌더링 될 때마다 이벤트 핸들러, props, state의 고유성을 보장하게 됩니다. 즉, 렌더링 될 때마다 각 렌더된 컴포넌트들은 각자의 props, state, 함수를 갖고 있다는 것입니다.
따라서 가장 중요한점은
함수형 컴포넌트는 렌더링된 값들을 고정시킨다는 것이고 클래스형 컴포넌트는 고정시키지 않습니다.
그렇다면 클래스형 컴포넌트는 함수형 컴포넌트처럼 값을 고정시키려면 어떻게 해야할까요?
componentDidUpdate함수 내에서 const number을 따로 지정해주면 됩니다..!
import { Component } from 'react';
class App extends Component {
state = {
number: 0
};
componentDidUpdate() {
const number = this.state.number;
setTimeout(() => {
console.log(`${number}`);
}, 3000);
}
render() {
return (
<div>
<p>현재 숫자는 {this.state.number} 입니다.</p>
<button onClick={() => this.setState({number: this.state.number + 1})}>1씩 올리기</button>
</div>
)
}
}
export default App;
참고자료
https://overreacted.io/ko/a-complete-guide-to-useeffect/
useEffect 완벽 가이드
이펙트는 데이터 흐름의 한 부분입니다.
overreacted.io