Custom Hooks
개발자가 스스로 커스텀한 훅을 의미, 반복되는 로직을 함수로 뽑아내어 재사용
- 여러 url을 fetch할 때, 여러 input에 의한 상태 변경 등 반복되는 로직을 동일한 함수에서 작동하게 하고 싶을 때 커스텀 훅을 주로 사용
컴포넌트가 존재합니다(React 공식 문서에 존재하는 컴포넌트)
//FriendStatus : 친구가 online인지 offline인지 return하는 컴포넌트
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
//FriendListItem : 친구가 online일 때 초록색으로 표시하는 컴포넌트
function FriendListItem(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
FriendStatus와 FriendListItem 컴포넌트는 정확하게 똑같이 쓰이는 로직이 존재한다.
이 로직을 빼내서 Custom Hook을 작성할 수 있다.
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;
}
두 컴포넌트에서 사용하기 위해 동일하게 사용되고 있는 로직을 분리하여 함수
useFriendStatus로 작성
HOOK 정의 시 규칙
1. Custom Hook 정의 시 함수 이름 앞에 use 붙이기
2. 대개의 경우 프로젝트 내의 hooks 디렉토리에 Custom Hook 위치 시키기
3. Custom Hook으로 만들 때 함수는 조건부 함수가 아니어야 한다.
-> return 하는 값은 조건부여서는 안된다.
-> useFriendStatus Hook은 온라인 상태의 여부를 boolean 타입으로 반환
Custom Hook은 Hook 내부에 useState와 같은 React 내장 Hook을 사용하여 작성할 수 있다.
❗️ 일반 함수 내부에서는 내장 Hook을 불러 사용 불가능하다.
useFriendStatus을 두 컴포넌트에 적용 시키기
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>
);
}
분리전
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [data, setData] = useState();
useEffect(() => {
fetch("data.json", {
headers: {
"Content-Type": "application/json",
Accept: "application/json"
}
})
.then((response) => {
return response.json();
})
.then((myJson) => {
setData(myJson);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div className="App">
<h1>To do List</h1>
<div className="todo-list">
{data &&
data.todo.map((el) => {
return <li key={el.id}>{el.todo}</li>;
})}
</div>
</div>
);
}
분리 후
// src/util/useFetchData.js
import {useState, useEffect} from "react";
const userFetchData = (url) => {
const [data, setData]
useEffect(() => {
fetch(url, {
hearders:{
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then((response) => {
return response.json()
})
.then((myJson) => {
setData(myJson);
})
.catch((err) => {
console.log(err)
});
}, [url];
return data;
}
export default useFetchData;
//App.js
import "./styles.css";
import useFetchData from "./util/useFetchData";
// import { useEffect, useState } from "react";
export default function App() {
const data = useFetchData("data.json")
return (
<div className="App">
<h1>To do List</h1>
<div className="todo-list">
{data &&
data.todo.map((el) => {
return <li key={el.id}>{el.todo}</li>;
})}
</div>
</div>
);
}
분리 전 : 낮은 가독성
import { useState } from "react";
import "./styles.css";
export default function App() {
//input에 들어가는 상태값 및 로직을 custom hook으로 만들어봅니다.
//until 폴더의 useInput.js 파일이 만들어져 있습니다.
const [firstNameValue, setFirstNameValue] = useState("");
const [lastNameValue, setLastNameValue] = useState("");
const [nameArr, setNameArr] = useState([]);
const handleSubmit = (e) => {
e.preventDefault();
setNameArr([...nameArr, `${firstNameValue} ${lastNameValue}`]);
};
return (
<div className="App">
<h1>Name List</h1>
<div className="name-form">
<form onSubmit={handleSubmit}>
<div className="name-input">
<label>성</label>
<input
value={firstNameValue}
onChange={(e) => setFirstNameValue(e.target.value)}
type="text"
/>
</div>
<div className="name-input">
<label>이름</label>
<input
value={lastNameValue}
onChange={(e) => setLastNameValue(e.target.value)}
type="text"
/>
</div>
<button>제출</button>
</form>
</div>
<div className="name-list-wrap">
<div className="name-list">
{nameArr.map((el, idx) => {
return <p key={idx}>{el}</p>;
})}
</div>
</div>
</div>
);
}
분리 후