리액트 컴포넌트는 클래스형 컴포넌트와 함수형 컴포넌트로 나누어진다.
현재 리액트 공식 문서에서 함수형 컴포넌트와 Hooks을 사용하는 것을 권장하고 있지만,
기존에는 함수형 컴포넌트가 존재하지 않았기 때문에 클래스형 컴포넌트를 사용하여 작업하였다.
우리는 현재 함수형 컴포넌트와 React Hooks을 익숙하고 당연한 듯이 사용하지만,
정작 왜 함수형 컴포넌트가 더 권장되고 있는지 명확하게 알지 못한다.
따라서 기존에 사용했던 클래스형 컴포넌트에 대해 알아보고
함수형 컴포넌트와 React Hooks가 어떻게 등장하게 되었는지 이해하고자 한다.
기존의 클래스형 컴포넌트의 문제점을 함수형 컴포넌트와 React Hooks로써
어떻게 극복하게 되었는지 그 과정을 찬찬히 공부해보자!
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
id: 1,
msg: "안녕하세요!",
};
}
render() {
const { name, age } = this.props;
return (
<div>
<div>{this.state.msg}</div>
<div>이름: {name}</div>
<div>나이: {age}</div>
</div>
);
}
}
export default App;
클래스형 컴포넌트를 이용해 만들어낸 컴포넌트는 말 그대로 하나의 객체처럼 동작한다.
this
를 통해 자기 자신을 칭하고 뭔가 변화가 생기면 render()
메서드를 다시 호출해 리렌더링을 한다.
constructor
은 컴포넌트가 처음 생성될 때 호출되는 메소드이다.
state
을 초기화하거나 props
을 부모 컴포넌트로부터 받아오는 데 사용된다.
🚨 주의사항
생성자 내부에서는 super(props)
을 반드시 호출해야 한다!
그렇지 않으면 this.props
가 제대로 초기화되지 않아 props
에 접근할 수 없다.
또한 this.state
에 직접 값을 할당하는 것은 생성자에서만 가능하다.
그 외에는 반드시 setState()
을 통해 상태를 변경해야 한다.
컴포넌트를 DOM에 부착하여 실질적으로 화면에 렌더링하는 메소드이다.
클래스형 컴포넌트에서는 render()
메소드를 반드시 구현해야 한다!
이 메서드에서 렌더링하고 싶은 JSX을 반환하면 된다.
🚨 주의사항
render 함수 내에서 props을 가져오는 코드를 작성해야 한다.
const { name, age } = this.props
그 이유는 컴포넌트가 render
될 때마다 props
가 최신 상태로 유지되어야 하기 때문이다.
만약 render
함수 밖에서 props
을 한 번만 가져온다면,
이후에 props
가 변경되더라도 최신 값이 반영되지 않아 원하는 대로 동작하지 않는다.
따라서 props
는 반드시 render
함수 안에서 정의하여야 한다!
이 밖에도 클래스형 컴포넌트에서 사용할 수 있는 다양한 생명 주기 메소드들이 존재한다.
이와 관련해서는 아래의 글에 따로 정리해두었으니 궁금하신 분들은 읽기를 추천한다!
📎 클래스형 컴포넌트의 LifeCycle Method
https://velog.io/@okxooxoo/React-LifeCycle-Method
클래스형 컴포넌트에서 state
와 props
을 사용할 때 자기 참조 변수인 this
을 사용한다.
자바스크립트에서 this
는 런타임에 바인딩되기 때문에 mutable
하다.
이러한 점은 this
가 어떤 객체에 바인딩되었는지 추적할 수 없게 만든다.
만약 이벤트 핸들러나 콜백 함수 내부에서 this
를 사용하면
this
가 컴포넌트를 가리키지 않게 되어 state
와 props
에 접근할 수 없다.
명시적으로 this
을 바인딩하는 방법도 존재하지만 코드가 복잡해지고 개발자들에게 혼란을 줄 수 있다.
또한 클래스형 컴포넌트는 여러 생명 주기 메소드에 상태 관리를 분산시켜야 한다.
하나의 기능과 관련된 코드가 서로 다른 메소드에 분리되는 경우가 많기 때문에
이로 인해 코드가 중복되거나 비효율적으로 작성될 수 있다.
그리고 클래스형 컴포넌트는 함수형 컴포넌트보다 메모리 자원을 더 많이 사용한다!
위의 문제점들을 극복하기 위해 React에 함수형 컴포넌트와 Hook이 도입되었다.
import React, { useState } from 'react';
const App = ({ name, age }) => {
const [msg, setMsg] = useState("안녕하세요!");
return (
<div>
<div>{msg}</div>
<div>이름: {name}</div>
<div>나이: {age}</div>
</div>
);
};
export default App;
함수형 컴포넌트는 클래스형 컴포넌트에 비해 훨씬 간결한 문법을 제공한다.
this
키워드에 대한 고민 없이 props
와 state
을 직접적으로 사용할 수 있다!
그러나 클래스형 컴포넌트에서 제공하는 생명 주기 메소드를 사용할 수 없다는 한계가 있다.
이러한 한계를 극복하기 위해 Hooks가 도입되었다.
Hooks는 클래스형 컴포넌트의 고유 기능인 상태 관리와 라이프사이클 관리 기능을
함수형 컴포넌트에서도 사용할 수 있도록 해주는 함수들을 총칭한다.
React Hooks을 사용하면 더 간편하게 state와 side effect을 관리할 수 있다!
React Hooks에는 다음과 같은 종류가 존재한다.
useState
useReducer
useContext
useRef
useEffect
useLayoutEffect
useInsertionEffect
useMemo
useCallback
useTransition
useDeferredValue
함수형 컴포넌트는 state
와 props
의 불변성을 유지하기 쉽다는 장점이 있다!
이상하다...
React 프로젝트를 진행해봤다면 state
와 props
의 값이 충분히 변경 가능하다는 사실을 알 것이다.
React에서 state
와 props
가 불변하다는 것은 무슨 의미일까?
💡 이는 props와 state을 직접적으로 수정할 수 없다는 의미이다.
한 번 생성된 props
와 state
객체는 원본을 변경하지 않는다는 원칙을 따르며,
그 값을 직접 변경하지 않고 새로운 객체로 대체해야 한다.
props
부모 컴포넌트가 자식 컴포넌트에게 전달하는 데이터이다.
자식 컴포넌트는 전달받은 props
을 읽기 전용으로 받아들여 사용한다.
state
컴포넌트가 자체적으로 관리하는 상태로 이 또한 직접 수정할 수 없다.
useState
의 setter 함수를 이용하여 새로운 상태로 대체하면서 컴포넌트의 리렌더링을 트리거한다.
🤔 그렇다면 왜 불변성을 유지해야 할까?
리액트는 state
나 props
가 변경되면 컴포넌트를 다시 렌더링하여 변경된 부분을 화면에 반영해야 한다.
위 작업을 효율적으로 하기 위해 변경 여부를 쉽게 감지할 수 있어야 하는데,
만약 state
와 props
가 직접적으로 수정된다면 어떤 부분이 변했는지 추적하기 어려워지고
불필요한 렌더링이 발생하거나 리액트가 변경 사항을 놓칠 수 있다.
아래의 예시를 살펴보자!
// 직접 state를 변경하는 방식
this.state.count = 5;
// 올바른 state 변경
setCount(5);
직접 state
를 변경하는 방식에서는 리액트가 그 객체가 변했는지 여부를 쉽게 감지할 수 없다.
왜냐하면 객체의 속성을 변경하더라도 메모리 상에서 같은 객체가 참조되고 있기 때문이다.
그러나 useState
의 setter을 사용하면 리액트는 새로운 상태 객체를 생성하여 컴포넌트의 상태를 변경한다.
위의 방식을 통해 이전 상태와 현재 상태를 ===
연산을 통해 간단하게 비교할 수 있다.
리액트는 어떠한 부분이 변경되었는지 효율적으로 판단하고 변경된 부분만 다시 렌더링 한다!
📎 참고 자료
https://medium.com/hcleedev/web-react-hooks의-등장-배경과-의의-d400cbc203a4