커스텀 훅을 만들면 반복되는 컴포넌트 로직을 함수로 만들어서 재사용할 수 있다.
채팅 어플리케이션에서 사용자가 접속중이면 접속중인 상태는 검정색 접속중이 아닐때는 검정색으로 상 나타내기로 했을 때, 이 상태 표시창을 여러 컴포넌트 ( 친구 정보, 친구리스트) 에서 해야 한다면 어떻게 해야 할까?
각 컴포넌트 안에 state를 만들고 useEffect를 통해 서버로부터 접속 상태를 얻어올 수 있다.
그러나 이 방식을 사용하면 완벽하게 똑같은 로직 (서버와 통신하는 코드)를 중복하여 두 컴포넌트안에 작성하게 된다.
이렇게 중복되는 로직을 하나의 기능을 담당하는 함수로 분리하여 코드를 작성하는것 처럼
커스텀 훅을 사용하여 재사용할 수 있다.
React Custom Hook
두 개의 자바스크립트 함수에서 같은 로직을 공유하고자 할 때 함수로 분리하여 사용하는 것 처럼 컴포넌트와 Hook 또한 함수이므로 같은 방식을 적용할 수 있다.
Custom Hook 만들기
커스텀 훅은 use로 시작하는 자바스크립트 함수이다.
커스텀 훅을 생성할 땐 반드시 use
로 시작하는 이름을 지어야 한다.
사용자 훅은 인수로 받을 값, 리턴할 값을 사용자가 직접 결정할 수 있다.
import { useState, useEffect } from 'react';
// 이름은 꼭 use로 시작해야함.
function useFriendStatus(friendID) { // 무엇을 인수로 받을지 사용자가 정한다.
const [isOnline, setIsOnline] = useState(null);
useEffect(() => { // 접속 상태를 얻기 위한 서버 통신 코드
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline; // 무엇을 리턴할지는 사용자가 정한다.
}
위 코드는 사용자의 상태를 구독하기 위함인데, 이를 위해 userID를 인수로 받고 온라인 상태 여부를 반환한다.
Custom Hook 이용하기
유저의 접속 상태창(FrendStatus) 컴포넌트와 유저 리스트(FriendListItem)에서 해당 유저의 온라인 상태 여부 로직을 사용해야 했다.
위에서 작성한 커스텀 훅을 활용하여 두 컴포넌트에서 접속 상태를 불러온다.
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
각각의 컴포넌트에서 온라인 여부를 알고싶은 유저의 id만 우리의 커스텀 훅으로 전달 해 주면 커스텀 훅에서 유저의 온라인 상태를 리턴해준다.
같은 Hook을 사용하는 두개의 컴포넌트는 state를 공유할까?
답은 NO! 공유하지 않는다.
커스텀훅은 상태 관련 로직을 재사용하는 함수이다.
일반 js함수가 함수 내의 지역 변수를 리턴 할 때 항상 다른 값이 리턴 되는 것 처럼 커스텀 훅도 사용할 때 마다 state와 effect는 완전히 독립적이다.
A,B 컴포넌트에서 커스텀 훅인 C를 사용할 때,
A가 C로부터 받은 값을 업데이트해도 B에게는 전혀 영향을 미치지 않는다는 것을 말한다.
Hook에서 Hook으로 데이터 전달하기
Hook의 데이터 값을 또 다른 Hook으로 전달해 줄 수 있다.
친구 리스트를 select박스로 보여주고, 친구를 선택할 때 마다 해당 친구의 접속여부를 알고싶은 상황이다.
현재 컴포넌트의 state에 상태를 볼 id를 저장해둔다.
그리고 이 state를 위에서 만들어둔 접속 여부 커스텀훅에 인수로 전달하여 접속여부를 리턴 받는다.
select의 옵션이 바뀔 때 마다 해당 컴포넌트의 state가 변경 되므로 컴포넌트가 리렌더링 되면서 우리의 커스텀 훅도 새롭게 호출한다. 따라서 선택된 사람의 온라인 여부를 확인할 수 있다.
const friendList = [
{ id: 1, name: 'Phoebe' },
{ id: 2, name: 'Rachel' },
{ id: 3, name: 'Ross' },
];
function ChatRecipientPicker() {
const [recipientID, setRecipientID] = useState(1); // 온라인 여부 확인할 id state
const isRecipientOnline = useFriendStatus(recipientID);
return (
<>
<Circle color={isRecipientOnline ? 'green' : 'red'} />
<select
value={recipientID}
onChange={e => setRecipientID(Number(e.target.value))} // id변경되면 setState-> 컴포넌트 리렌더링
>
{friendList.map(friend => (
<option key={friend.id} value={friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}
Reducer 커스텀 훅 만들기
상태관리 라이브러리인 Redux의 Reducer를 사용하면 많은 state를 손쉽게 관리할 수 있다.
그러나 Redux를 사용하고 싶지 않을 땐 어떻게 해야 할까?
Reducer처럼 동작하는 커스텀 훅 useReducer
를 만들어 볼 수있다.
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
작성한 커스텀 훅을 다른 컴포넌트안에서 사용 할 수 있다.
function Todos() {
const [todos, dispatch] = useReducer(todosReducer, []);
function handleAddClick(text) {
dispatch({ type: 'add', text });
}
// ...
}
복잡한 컴포넌트 내에서 state를 reducer로 관리해야 하는 보편적인 필요성을 고려하여 React는 useReducer
가 내장되어 있다.
useReducer
useState의 대체 함수. (state,action)=>newState의 형태로 reducer를 받고 dispatch 메서드와 짝의 형태로 state를 반환한다.
다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우나 다음 state가 이전 state에 의존적이라면 useState보다 useReducer를 사용하는 것을 선호한다.
useReducer는 콜백 대신 dispatch를 전달 할 수 있으므로 컴포넌트 성능을 최적화 할 수 있다. 콜백전달을 피하는법