Search

Custom Hook

React Custom Hook (μ»€μŠ€ν…€ ν›…)

1. μ»€μŠ€ν…€ ν›…μ΄λž€?

μ»€μŠ€ν…€ ν›…(Custom Hook)은 React의 λ‚΄μž₯ ν›…(useState, useEffect λ“±)을 μ‘°ν•©ν•˜μ—¬ λ°˜λ³΅λ˜λŠ” μƒνƒœ λ‘œμ§μ„ μž¬μ‚¬μš© κ°€λŠ₯ν•œ ν•¨μˆ˜λ‘œ μΆ”μΆœν•œ κ²ƒμž…λ‹ˆλ‹€.

핡심 κ·œμΉ™

β€’
ν•¨μˆ˜ 이름은 λ°˜λ“œμ‹œ use둜 μ‹œμž‘ν•΄μ•Ό ν•©λ‹ˆλ‹€.
β€’
λ‚΄λΆ€μ—μ„œ λ‹€λ₯Έ 훅을 ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€.
β€’
μ»΄ν¬λ„ŒνŠΈκ°€ μ•„λ‹Œ 일반 JavaScript ν•¨μˆ˜μ΄μ§€λ§Œ, ν›…μ˜ κ·œμΉ™μ„ λ”°λ¦…λ‹ˆλ‹€.
일반 ν•¨μˆ˜ β†’ getUser() μ»€μŠ€ν…€ ν›… β†’ useUser()
Plain Text
볡사

2. μ™œ μ»€μŠ€ν…€ 훅을 μ‚¬μš©ν•˜λŠ”κ°€?

문제 상황
μ»€μŠ€ν…€ ν›… λ„μž… ν›„
μ—¬λŸ¬ μ»΄ν¬λ„ŒνŠΈμ— λ™μΌν•œ 둜직 쀑볡
ν›… ν•˜λ‚˜λ‘œ 곡유
μ»΄ν¬λ„ŒνŠΈκ°€ λ„ˆλ¬΄ 컀지고 λ³΅μž‘ν•΄μ§
λ‘œμ§μ„ λΆ„λ¦¬ν•˜μ—¬ μ»΄ν¬λ„ŒνŠΈ λ‹¨μˆœν™”
둜직 ν…ŒμŠ€νŠΈκ°€ 어렀움
ν›… λ‹¨μœ„λ‘œ 독립 ν…ŒμŠ€νŠΈ κ°€λŠ₯
둜직 μˆ˜μ • μ‹œ μ—¬λŸ¬ 파일 λ³€κ²½ ν•„μš”
ν›… 파일 ν•˜λ‚˜λ§Œ μˆ˜μ •

3. 기본 예제 - useCounter

μ»€μŠ€ν…€ ν›… 없이 (쀑볡 μ½”λ“œ λ°œμƒ)

// ComponentA.jsx const [count, setCount] = useState(0); const increment = () => setCount(c => c + 1); const decrement = () => setCount(c => c - 1); const reset = () => setCount(0); // ComponentB.jsx (λ™μΌν•œ μ½”λ“œ 반볡) const [count, setCount] = useState(0); const increment = () => setCount(c => c + 1); const decrement = () => setCount(c => c - 1); const reset = () => setCount(0);
JavaScript
볡사

μ»€μŠ€ν…€ ν›…μœΌλ‘œ 뢄리

// hooks/useCounter.js import { useState } from 'react'; function useCounter(initialValue = 0) { const [count, setCount] = useState(initialValue); const increment = () => setCount(c => c + 1); const decrement = () => setCount(c => c - 1); const reset = () => setCount(initialValue); return { count, increment, decrement, reset }; } export default useCounter;
JavaScript
볡사

μ‚¬μš©ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈ

// ComponentA.jsx import useCounter from './hooks/useCounter'; function ComponentA() { const { count, increment, decrement, reset } = useCounter(10); return ( <div> <p>카운트: {count}</p> <button onClick={increment}>+1</button> <button onClick={decrement}>-1</button> <button onClick={reset}>μ΄ˆκΈ°ν™”</button> </div> ); }
JavaScript
볡사

4. μ‹€μš© 예제 - useFetch

API 호좜 λ‘œμ§μ„ μž¬μ‚¬μš© κ°€λŠ₯ν•˜κ²Œ μΆ”μΆœν•©λ‹ˆλ‹€.
// hooks/useFetch.js import { useState, useEffect } from 'react'; function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { if (!url) return; setLoading(true); setError(null); fetch(url) .then(res => { if (!res.ok) throw new Error('μ„œλ²„ 였λ₯˜: ' + res.status); return res.json(); }) .then(json => setData(json)) .catch(err => setError(err.message)) .finally(() => setLoading(false)); }, [url]); return { data, loading, error }; } export default useFetch;
JavaScript
볡사

μ‚¬μš© μ˜ˆμ‹œ

import useFetch from './hooks/useFetch'; function UserList() { const { data: users, loading, error } = useFetch('<https://jsonplaceholder.typicode.com/users>'); if (loading) return <p>λ‘œλ”© 쀑...</p>; if (error) return <p>μ—λŸ¬: {error}</p>; return ( <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }
JavaScript
볡사

5. μ‹€μš© 예제 - useLocalStorage

localStorage와 μƒνƒœλ₯Ό λ™κΈ°ν™”ν•©λ‹ˆλ‹€.
// hooks/useLocalStorage.js import { useState } from 'react'; function useLocalStorage(key, initialValue) { const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { return initialValue; } }); const setValue = (value) => { try { setStoredValue(value); window.localStorage.setItem(key, JSON.stringify(value)); } catch (error) { console.error(error); } }; return [storedValue, setValue]; } export default useLocalStorage;
JavaScript
볡사

μ‚¬μš© μ˜ˆμ‹œ

import useLocalStorage from './hooks/useLocalStorage'; function ThemeToggle() { const [theme, setTheme] = useLocalStorage('theme', 'light'); return ( <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> ν˜„μž¬ ν…Œλ§ˆ: {theme} </button> ); }
JavaScript
볡사

6. μ‹€μš© 예제 - useInput

폼 μž…λ ₯ μƒνƒœλ₯Ό κ΄€λ¦¬ν•©λ‹ˆλ‹€.
// hooks/useInput.js import { useState } from 'react'; function useInput(initialValue = '') { const [value, setValue] = useState(initialValue); const onChange = (e) => setValue(e.target.value); const reset = () => setValue(initialValue); return { value, onChange, reset }; } export default useInput;
JavaScript
볡사

μ‚¬μš© μ˜ˆμ‹œ

import useInput from './hooks/useInput'; function LoginForm() { const email = useInput(''); const password = useInput(''); const handleSubmit = (e) => { e.preventDefault(); console.log(email.value, password.value); email.reset(); password.reset(); }; return ( <form onSubmit={handleSubmit}> <input type="email" placeholder="이메일" {...email} /> <input type="password" placeholder="λΉ„λ°€λ²ˆν˜Έ" {...password} /> <button type="submit">둜그인</button> </form> ); }
JavaScript
볡사
μŠ€ν”„λ ˆλ“œ ν™œμš©: {...email}은 value={email.value} onChange={email.onChange}와 λ™μΌν•©λ‹ˆλ‹€.

7. μ‹€μš© 예제 - useDebounce

μž…λ ₯값이 일정 μ‹œκ°„ λ™μ•ˆ λ³€ν•˜μ§€ μ•ŠμœΌλ©΄ μ μš©ν•©λ‹ˆλ‹€ (검색 μ΅œμ ν™”μ— 유용).
// hooks/useDebounce.js import { useState, useEffect } from 'react'; function useDebounce(value, delay = 500) { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const timer = setTimeout(() => { setDebouncedValue(value); }, delay); return () => clearTimeout(timer); // 클린업 }, [value, delay]); return debouncedValue; } export default useDebounce;
JavaScript
볡사

μ‚¬μš© μ˜ˆμ‹œ

import { useState } from 'react'; import useDebounce from './hooks/useDebounce'; function SearchBox() { const [query, setQuery] = useState(''); const debouncedQuery = useDebounce(query, 500); // debouncedQueryκ°€ 변경될 λ•Œλ§Œ API 호좜 useEffect(() => { if (debouncedQuery) { console.log('검색 API 호좜:', debouncedQuery); } }, [debouncedQuery]); return ( <input value={query} onChange={e => setQuery(e.target.value)} placeholder="검색어 μž…λ ₯..." /> ); }
JavaScript
볡사

8. μ»€μŠ€ν…€ ν›… μž‘μ„± κ·œμΉ™ μš”μ•½

κ·œμΉ™
μ„€λͺ…
use 접두사
이름이 λ°˜λ“œμ‹œ use둜 μ‹œμž‘ν•΄μ•Ό 함
ν›…μ˜ κ·œμΉ™ μ€€μˆ˜
μ΅œμƒμœ„ λ ˆλ²¨μ—μ„œλ§Œ 호좜, 쑰건문/반볡문 λ‚΄ 호좜 κΈˆμ§€
파일 μœ„μΉ˜
보톡 src/hooks/ 폴더에 λͺ¨μŒ
단일 μ±…μž„
ν•˜λ‚˜μ˜ 훅은 ν•˜λ‚˜μ˜ κ΄€μ‹¬μ‚¬λ§Œ λ‹΄λ‹Ή
λ°˜ν™˜κ°’
객체 λ˜λŠ” λ°°μ—΄λ‘œ ν•„μš”ν•œ κ°’κ³Ό ν•¨μˆ˜λ₯Ό λ°˜ν™˜

9. 폴더 ꡬ쑰 ꢌμž₯ μ˜ˆμ‹œ

src/ β”œβ”€β”€ components/ β”‚ β”œβ”€β”€ UserList.jsx β”‚ └── LoginForm.jsx β”œβ”€β”€ hooks/ β”‚ β”œβ”€β”€ useCounter.js β”‚ β”œβ”€β”€ useFetch.js β”‚ β”œβ”€β”€ useInput.js β”‚ β”œβ”€β”€ useLocalStorage.js β”‚ └── useDebounce.js └── App.jsx
Plain Text
볡사

10. μ»€μŠ€ν…€ ν›… vs 일반 ν•¨μˆ˜

ꡬ뢄
μ»€μŠ€ν…€ ν›…
일반 ν•¨μˆ˜
이름
use둜 μ‹œμž‘
자유둭게 μž‘λͺ…
λ‚΄λΆ€ ν›… μ‚¬μš©
κ°€λŠ₯ (useState, useEffect λ“±)
λΆˆκ°€λŠ₯
μƒνƒœ 관리
κ°€λŠ₯
λΆˆκ°€λŠ₯
μ‚¬μ΄λ“œ μ΄νŽ™νŠΈ
κ°€λŠ₯
λΆˆκ°€λŠ₯
호좜 μœ„μΉ˜
μ»΄ν¬λ„ŒνŠΈ λ˜λŠ” λ‹€λ₯Έ μ»€μŠ€ν…€ ν›… λ‚΄λΆ€λ§Œ
μ–΄λ””μ„œλ“  κ°€λŠ₯

핡심 정리

μ»€μŠ€ν…€ 훅은 "둜직의 μž¬μ‚¬μš©"을 μœ„ν•œ λ„κ΅¬μž…λ‹ˆλ‹€. UI(JSX)λŠ” μ»΄ν¬λ„ŒνŠΈλ‘œ λΆ„λ¦¬ν•˜κ³ , μƒνƒœ λ‘œμ§μ€ μ»€μŠ€ν…€ ν›…μœΌλ‘œ λΆ„λ¦¬ν•˜λŠ” 것이 React의 ꢌμž₯ νŒ¨ν„΄μž…λ‹ˆλ‹€.
μ»΄ν¬λ„ŒνŠΈ = UI (JSX) + μ»€μŠ€ν…€ ν›… 호좜
μ»€μŠ€ν…€ ν›… = μƒνƒœ 둜직 + μ‚¬μ΄λ“œ μ΄νŽ™νŠΈ 둜직