티스토리 뷰
React 16.8 버전부터 FC - Functional Component(함수형 컴포넌트)에 State를 사용할 수 있도록 해 주는 Hooks 라는 개념이 생겼습니다. 스스로 Hooks 는 별로 중요한 기능이 아니라고 생각했었지만, React 개발자들 사이에서는 Hooks 가 중요하게 여겨지고 있었습니다. 이에 의문을 가지고 Hooks 와 함수형 컴포넌트에 대해서 학습하였고 함수형 컴포넌트와 Hooks 의 중요성에 공감하게 되어 이번 포스팅에서는 이에 대해 정리해보고자 합니다.
■ 왜 Functional Component(함수형 컴포넌트)에 집중하는가
Hook을 보며 많은 개발자들이 클래스 컴포넌트보다 함수형 컴포넌트를 더 선호하고 둘의 차이를 좁히기위해 노력하고 있다는걸 알게되었습니다. 그래서 함수형 컴포넌트를 선호하는 이유에 대해 먼저 알아보려고 합니다.
// CLASS COMPONENT
class TestComponent extends React.Component {
render() {
return (
<h1>
테스트 {this.props.text}
</h1>
)
}
}
// FUNCTIONAL COMPONENT
function TestComponent(props) {
return (
<h1>
테스트 {this.props.text}
</h1>
)
}
위의 예제와 같이 클래스 컴포넌트는 클래스를 기반으로 작성되는 컴포넌트이며, 함수형 컴포넌트는 함수를 기반으로 작성되는 함수형 컴포넌트 입니다. 두 컴포넌트의 가장 큰 차이점은 라이프 사이클이라고 할 수 있습니다.
클래스 컴포넌트는 componentDidMount, getDerivedStateFromProps등의 React Component의 라이프사이클을 사용할 수 있지만 기존의 함수형 컴포넌트는 이러한 라이프사이클을 사용할 수 없었습니다. 그래서 온전히 render 만 구현할 수 있었다고 볼 수 있습니다.
(이러한 라이프사이클을 함수형 컴포넌트에서도 사용할 수 있도록 Hooks가 등장했지만 이는 잠시 뒤에 설명하도록 하겠습니다)
그렇다면 기능도 부실한 함수형 컴포넌트가 무슨 장점이 있는건가 의문이 드실겁니다. 이는 클래스 컴포넌트의 문제점에 있습니다. 간단한 예제를 살펴보겠습니다.
위의 두 컴포넌트는 사용자를 선택하고 follow 버튼을 누르면 3초 이후에(setTimeout) 선택한 사용자의 이름을 출력하는 예제입니다. 영상을 보면 두 컴포넌트 모두 정상동작하는 것 처럼 보이지만 클래스 컴포넌트에서 유저를 follow 한 후 3초가 지나기 이전에 다른 유저를 follow 하면 첫번째 유저가 아닌 두번째로 선택한 유저에대한 알림이 발생합니다.
클래스 컴포넌트는 props를 재사용 하기 때문에 생기는 문제 입니다. 타임아웃이 지나기전에 props가 두 번째 유저로 변경되었기 때문에 두 번째 유저가 출력되는 것 입니다. 자세한 내용은 Overreacted에서 확인하실 수 있습니다. 간단히 정리하자면 클래스 컴포넌트는 props를 재사용하기 때문에 개발자의 예상과 다르게 동작할 수 있는 문제점이 존재합니다. 따라서 별도의 예방 작업을 해 주어야 하지만 함수형 컴포넌트는 이러한 문제점이 발생하지 않습니다.
이 외에도 함수형 컴포넌트는 클래스 컴포넌트에 비해 성능과 가독성이 좋고 테스트 하기 쉽습니다. 또한 말그대로 함수형 컴포넌트이기에 함수형 프로그래밍의 장점을 활용하기에도 유리합니다.
■ Hooks - Functional Component에 날개를 달다(?)
React 16.8 버전부터 함수형 컴포넌트에서 state와 라이프사이클을 사용할 수 있는 Hook이라는 개념이 등장했습니다. 때문에 기존에 동일시 되었던 Stateless Component와 Functional Component는 개념적으로 분리가 되었다고 볼 수 있습니다.
■ State hook
Hooks에는 state를 사용할 수 있는 useState함수가 추가되었습니다. 인자로 초기 state값을 넣어주면 state와 state를 업데이트 시켜주는 함수가 배열 형태로 반환됩니다. 아래의 간단한 count 예제를 통해 자세히 살펴보겠습니다.
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
초기값이 0인 state를 선언하였고 버튼을 클릭하면 count를 증가시키는 컴포넌트 입니다. 위처럼 useState를 사용하면 기존에 state를 활용할 수 없었던 함수형 컴포넌트에서도 state를 사용할 수 있습니다. 사용하는 state의 수는 제한이 없기때문에 useState를 여러번 사용해도 됩니다.
■ Effect Hook
state hook외에도 비교적 간단한 라이프사이클을 사용하기 위해 useEffect라는 함수도 추가되었습니다. useEffect는 컴포넌트가 마운트 되었을 때(componentDidMount), 컴포넌트가 업데이트 되었을 때(componentDidUpdate), 컴포넌트가 마운트 해제될때(componentWillUnmount) 세가지 경우의 라이프 사이클을 사용할 수 있습니다.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위의 예제는 componentDidMount와 componentDidUpdate 라이프사이클과 동일하게 동작하는 effect hook예제 입니다. 위의 예제처럼 기본적인 effect hook는 컴포넌트가 렌더링 될 때마다 호출됩니다.
두 번째 예제는 componentWillUnmount 라이프사이클과 비슷한 동작을 하는 예제 입니다.
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
effect hook의 반환값으로 함수를 반환해주는데요, 반환해준 함수는 컴포넌트의 마운트가 해제될때나 컴포넌트가 업데이트 되어 다음 effect가 호출되기 이전에 호출됩니다. 따라서 함수형 컴포넌트에서 componentWillUnmount기능이 필요한 경우 위의 예제처럼 사용할 수 있습니다.
아래 마지막 예제는 componentDidMount처럼 오직 컴포넌트가 마운트 되었을 때에만 effect hook를 호출하는 방법입니다.
import React, { useState, useEffect } from 'react';
function ComponentDidMountExample(props) {
useEffect(() => {
TestAPI.request();
}, []);
return (
<div>
test
</div>
);
}
useEffect의 두번째 인자로 props나 state값을 배열로 넣어줄 수 있습니다. 따라서 넣어준 값들이 변경되었을 때에만 effect hook가 호출됩니다. 하지만 빈 배열을 넣어준다면 업데이트를 감시할 필요가 없기 때문에 컴포넌트가 마운트 되었을때만 effect hook가 호출되어 componentDidMount처럼 동작할 수 있습니다.
■ 마치며
React Hooks 는 점진적으로 클래스컴포넌트에서만 존재하는 기능들을 옮겨올예정이라고 합니다. 하지만 릴리즈된지 얼마 되지않아 버그나 문제점들도 존재할 수 있기 때문에 지금 당장 구현된 것들을 갈아엎고 급하게 도입하기보다 앞으로 추가되는 기능, 프로젝트들에 천천히 도입해보는것도 좋을 것 같습니다.
저의 계획은 앞으로 React를 진행하는 프로젝트에 Class Component는 최소화 시키고 대부분의 컴포넌트를 Functional Component로 작성하려고 합니다. Functional Component의 장점과 성능을 보면 사용하지 않을 이유가 없는것같습니다.
React Hooks 에 대한 자세한 내용은 React의 공식 문서에서 찾아볼 수 있습니다.