React에서 Hook이란, 함수형 컴포넌트에서 사용되는, state와 관련된 기술들을 모아서 일컫는 말이다. 대표적으로 useState()
와 useEffect()
가 있는데 리액트 공식문서 HOOK를 읽으면서 정리해보았다.
Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수입니다. Hook은 class 안에서는 동작하지 않습니다. 대신 class 없이 React를 사용할 수 있게 해주는 것입니다.
Hook은 React 16.8에 새로 추가된 기능이다. Hook은 class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있게 해준다.
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위는 버튼을 클릭하면 값이 증가하는 간단한 카운터 예시다. 여기서 useState()
가 바로 Hook이다. 이는 현재의 state 값과 이 값을 업데이트하는 함수를 쌍으로 제공한다. useState()
의 인자는 초기 state 값이다.
종종 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작해야 할 때가 있다. 우리는 이러한 동작을 effects 라고 한다. 이는 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이다.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
// 브라우저 API를 이용해 문서의 타이틀을 업데이트
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위는 리액트가 DOM을 업데이트한 뒤에 문서의 타이틀을 바꾸는 컴포넌트이다. useEffect()
를 사용하면, 리액트는 DOM을 렌더링할 때마다 effects 함수를 실행한다. 그리고, effects는 컴포넌트 안에 선언되어 있으므로 props와 state에 접근할 수 있다.
위 예제는 cleanup이 필요없는 작업이다. 그러나 어떤 상태를 subscribe하고자 하는 경우에는 cleanup이 필요하다. 안 그러면 memory leak이 생기기 때문이다.
cleanup이란, useEffect()에서 parameter로 넣은 함수의 return 함수이다. 컴포넌트의 unmount 이전 / update 직전에 어떠한 작업을 수행하고 싶다면 cleanup 함수를 반환해줘야 한다.
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를 사용하고, 구독을 해지해서 해제하는 예제이다. 이 예시에서 컴포넌트가 unmount될 때 리액트는 ChatAPI에서 구독을 해지한다.
보편적이지는 않지만, 유용한 내장 Hook이 몇 가지 더 있다. 바로 useContext()
와 useReducer()
이다.
function Example() {
const locale = useContext(LocaleContext);
const theme = useContext(ThemeContext);
// ...
}
useContext()
는 컴포넌트를 중첩하지 않고 리액트 context를 구독할 수 있게 해준다.
function Todos() {
const [todos, dispatch] = useReducer(todosReducer);
// ...
useReducer()
는 복잡한 컴포넌트들의 state를 reducer로 관리할 수 있게 해준다.
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Hook을 사용하면 this
없이 state에 직접 접근하여 호출할 수 있다. useState는 state 변수, 해당 변수를 갱신할 수 있는 함수 이 두 가지 쌍을 반환한다. 이것이 바로 const [count, setCount] = useState()
라고 쓰는 이유다.
useEffect()
를 이용하면 리액트에게 컴포넌트 렌더링 이후 어떤 일을 할지 알려줄 수 있다. 리액트는 넘겨받은 함수를 기억했다가 (이 함수가 effect) DOM 업데이트 이후 불러낸다. effect를 통해 문서 타이틀을 지정하거나, 데이터를 가져오거나 API를 불러낼 수 있다.
- Mount: DOM 객체가 생성되고 브라우저에 나타나는 것을 의미
- Update: 컴포넌트가 업데이트(props 또는 state 변경, 부모 컴포넌트가 리렌더링) 될 때의 과정
- Unmount: 컴포넌트가 DOM에서 제거되는 것을 의미
리액트의 calss 생명주기 메서드로 설명하자면, useEffect()
를 componentDidMount
와 componentDidUpdate
, componentWillUnmount
가 합쳐진 것으로 생각해도 좋다.
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
}
앞 절에서 언급했던 예제를 자세히 짚고 넘어가겠다. 위 코드에서 useEffect()
파라미터에 함수를 전달하고 있는데 이 함수가 바로 effect이다. 컴포넌트를 렌더링할 때 리액트는 이 effect를 기억하였다가 DOM을 업데이트한 이후 실행한다. 이는 매 렌더링 시 똑같이 적용된다.
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
이것도 앞 절에서 언급했지만 이번 절에서 자세히 말하고자 한다. 리액트는 컴포넌트의 마운트가 해지될 때 cleanup을 실행한다. 하지만 effect는 매 렌더링마다 실행된다. 따라서 리액트가 다음 차례의 effect를 실행하기 전에 이전 렌더링에서 파생된 effect를 정리하는 것이다.
앞절에서 컴포넌트의 unmount 이전 / update 직전에 어떠한 작업을 수행하고 싶을 때 반환하는 것이 cleanup 함수라고 했다.
useEffect(func, [])
useEffect(func, [특정값])
그리고 cleanup 함수를 사용하게되면, 작동 순서는 리렌더링 -> 이전 effect cleanup -> effect 로 실행이 된다.