Custom Hooks
개발자가 스스로 커스텀한 훅을 의미합니다
Custom Hooks은 코드 상에 반복되는 로직을 뽑아내어 재사용 하는 것을 말합니다
여러 url을 fetch 할때, 여러 input에 의한 상태변경 등 반복되는 로직을 동일한 함수에서 작동하고 싶을때 커스텀 훅을 사용합니다
(커스텀 훅도 일종의 함수, 다만 일반 함수와 다른 점은 커스텀 훅 내부에서 내장 hook을 불러 사용할 수 있다는 것)
아래의 두 컴포넌트는 동일한 로직을 포함하고 있습니다
//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>
);
}
두 컴포넌트에서 동일하게 사용되고 있는 로직을 분리할겁니다
그리고 useFriendStatus 라는 이름을 가진 함수로 만듭니다
// 비슷한 로직만 빼서 만든 커스텀 훅
function useFriendStatus(friendID){
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 isOnline;
}
< 커스텀 훅 규칙>
- Custom Hook을 정의할 때는 함수 이름 앞에 use 를 붙이는 것이 규칙입니다.
- 대개의 경우 프로젝트 내의 hooks 디렉토리(폴더)에 Custom Hook을 위치시킵니다
- return 하는 값은 조건부여서는 안 됩니다.
이제 이 useFriendStatus Hook을 두 컴포넌트에 적용해보겠습니다
//FriendStatus : 친구가 online인지 offline인지 return하는 컴포넌트
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id)
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
//FriendListItem : 친구가 online일 때 초록색으로 표시하는 컴포넌트
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id)
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
살펴보면 isOnline 이라는 변수에 useFriendStatus 훅을 담아주었습니다.
그리고 아래보면, 변수 isOnline 의 값에 따라 다른 결과가 리턴됩니다.
아래에서도 역시 isOnline 이라는 변수에 useFriendStatus 훅을 담아주었습니다
역시 아래에서 변수 isOnline 의 값에 따라 다른 결과가 리턴됩니다.
+
그러나 같은 Custom Hook을 사용했다고 해서 두개의 컴포넌트가 같은 state를 공유 하는 것은 아닙니다.
그저 로직만 공유할뿐 state는 각 컴포넌트 내에서 독립적으로 정의되어 있습니다
custom hook을 이용하여 input 로직 분리하기
import { useState } from "react";
import useInput from "./util/useInput";
import Input from "./component/Input";
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>
);
}
위의 코드에서 custom hook을 사용하여 input의 로직을 분리해보겠습니다
const [firstNameValue, setFirstNameValue] = useState("");
const [lastNameValue, setLastNameValue] = useState("");
두 state가 이렇게 선언이 되어있고,
value={firstNameValue} onChange={(e) => setFirstNameValue(e.target.value)}
value={lastNameValue} onChange={(e) => setLastNameValue(e.target.value)}
현재 input 태그에서 value 와 onChange 가 비슷한 로직을 보이고 있습니다
import {useState} from 'react'
function useInput() {
const [Value, setValue] = useState("");
const onChange = (e) =>
{setValue(e.target.value)}
return [Value, onChange];
}
export default useInput;
useInput.js 파일에 가서
useInput 훅 안에서 Value와 onChange을 정의해주고,
[Value, onChange]을 리턴해줍니다
useInput 훅을 적용하러 다시 App.js 파일로 돌아갑니다.
import { useState } from "react";
import useInput from "./util/useInput";
import Input from "./component/Input";
import "./styles.css";
export default function App() {
//input에 들어가는 상태값 및 로직을 custom hook으로 만들어봅니다.
//until 폴더의 useInput.js 파일이 만들어져 있습니다.
const [firstNameValue, onFirstChange] = useInput("");
const [lastNameValue, onLastChange] = useInput("");
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={onFirstChange}
type="text"
/>
</div>
<div className="name-input">
<label>이름</label>
<input
value={lastNameValue}
onChange={onLastChange}
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>
);
}
useInput에서 각각 첫번째 자리와 두번째 자리가 이렇게 생겼어요
[Value, onChange]
(1) const [firstNameValue, onFirstChange] = useInput("");
useInput("")은 useInput 커스텀 훅을 호출하여 반환된 배열을 구조 분해 할당합니다.
useInput 훅은 초기값으로 "" (빈 문자열)을 전달하고, firstNameValue와 onFirstChange 변수에 할당됩니다.
firstNameValue는 useInput 훅에서 관리되는 상태의 값입니다.
onFirstChange는 useInput 훅에서 반환된 함수로, firstNameValue의 값을 업데이트하는 데 사용됩니다.
(2) const [lastNameValue, onLastChange] = useInput("");
이 부분은 위의 코드와 동일한 패턴을 따릅니다.
useInput("")는 useInput 커스텀 훅을 호출하고 반환된 배열을 구조 분해 할당합니다.
lastNameValue는 useInput 훅에서 관리되는 상태의 값입니다.
onLastChange는 useInput 훅에서 반환된 함수로, lastNameValue의 값을 업데이트하는 데 사용됩니다.
실제로 Name List 창에 input에 성과 이름을 적고 제출을 눌렀더니
하단에 이름리스트가 생겼어요!~~
'React' 카테고리의 다른 글
Invalid hook call 오류 (0) | 2023.06.08 |
---|---|
Storybook 문서화하기 (2) | 2023.05.24 |
React.lazy() 와 Suspense (0) | 2023.05.23 |
useMemo 와 useCallback (0) | 2023.05.22 |
Virtual DOM과 React Diffing Algorithm (0) | 2023.05.20 |