[Component & Hook]Custom Hooks
개발자가 스스로 커스텀한 훅, 반복되는 로직을 함수로 뽑아내어 재사용
여러 url을 fetch할 때, 여러 input에 의한 상태 변경 등 반복되는 로직을 동일한 함수에서 작동하게 하고 싶을 때
- 상태관리 로직 재활용 가능
- 클래스 컴포넌트보다 적은 양의 코드로 동일한 로직을 구현
- 함수형으로 작성하기 때문에 보다 명료(e.g. useSomething)
//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>
);
}
두 컴포넌트는 정확하게 겹치는 로직이 있다. 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;
}
동일하게 사용되고 있는 부분을 함수로 만듬
Custom Hook 규칙
- 함수 이름 앞에 use
- 프로젝트 내 hooks 디렉토리에 Custom Hook 위치
- 함수는 조건부 함수가 아니어야 함. useFriendStatus Hook은 온라인 상태의 여부를 boolean 타입으로 반환함
Hook 내부에 useState와 같은 React 내장 Hook을 사용하여 작성
일반 함수 내부에서는 React 내장 Hook을 불러 사용할 수 없지만 Custom Hook 에서는 가능
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>
);
}
Custom Hook을 사용했다고 해서 두 개의 컴포넌트가 같은 state를 공유하는 것은 아님. state는 컴포넌트 내에서 독립적으로 정의 되어 있음
예시
const useFetch = (initialUrl: string) => {
const [url, setUrl] = useState(initialUrl);
const [value, setValue] = useState("");
const fetchData = () => axios.get(url).then(({ data }) => setValue(data));
useEffect(() => {
fetchData();
}, [url]);
return [value];
};
export default useFetch;
여러 url을 fetch할 때
import { useState, useCallback } from "react";
function useInputs(initialForm) {
const [form, setForm] = useState(initialForm);
// change
const onChange = useCallback((e) => {
const { name, value } = e.target;
setForm((form) => ({ ...form, [name]: value }));
}, []);
const reset = useCallback(() => setForm(initialForm), [initialForm]);
return [form, onChange, reset];
}
export default useInputs;
여러 input에 의한 상태 변경을 할 때
실습
component/input.js
function Input({ nameValue, setNameValue }) {
return (
<input
value={nameValue}
onChange={(e) => setNameValue(e.target.value)}
type="text"
/>
);
}
export default Input;
util/useInput.js
import { useState } from "react";
function useInput({ firstNameValue, lastNameValue }) {
const [nameArr, setNameArr] = useState([]);
const handleSubmit = (e) => {
e.preventDefault();
setNameArr([...nameArr, `${firstNameValue} ${lastNameValue}`]);
};
return { nameArr, handleSubmit };
}
export default useInput;
App.js
import { useState } from "react";
import useInput from "./util/useInput";
import Input from "./component/Input";
import "./styles.css";
export default function App() {
const [firstNameValue, setFirstNameValue] = useState("");
const [lastNameValue, setLastNameValue] = useState("");
const { nameArr, handleSubmit } = useInput({ firstNameValue, lastNameValue });
return (
<div className="App">
<h1>Name List</h1>
<div className="name-form">
<form onSubmit={handleSubmit}>
<div className="name-input">
<label>성</label>
<Input
setNameValue={setFirstNameValue}
nameValue={firstNameValue}
/>
</div>
<div className="name-input">
<label>이름</label>
<Input setNameValue={setLastNameValue} nameValue={lastNameValue} />
</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>
);
}
댓글남기기