Search

Hook

Hook

Hook ( 갈고리 ) : λΎ°μ‘±ν•˜κ³  κ΅¬λΆ€λŸ¬μ§„ λ‚ λ‘œ λ‹€λ₯Έ 물체에 κ±Έμ–΄ μ—°κ²°ν•˜λŠ” 도ꡬ
κ°ˆκ³ λ¦¬λŠ” 물건을 μž‘κ±°λ‚˜ κ±ΈκΈ° μœ„ν•΄ μ‚¬μš©ν•˜λŠ” λ„κ΅¬μž…λ‹ˆλ‹€. hook은 λ¦¬μ•‘νŠΈ μ»΄ν¬λ„ŒνŠΈμ—μ„œ μƒνƒœλ‚˜ 라이프사이클을 μž‘κ±°λ‚˜ κ±Έμ–΄μ„œ μ‚¬μš©ν•  수 μžˆλŠ” λ„κ΅¬λΌλŠ” μ˜λ―Έμ—μ„œ μœ λž˜λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
μ»΄ν¬λ„ŒνŠΈμ—μ„œ μƒνƒœμ™€ 라이프사이클 관리λ₯Ό μœ„ν•œ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ° 방식을 μ œκ³΅ν•˜λŠ” API
λ¦¬μ•‘νŠΈ 16.8 버전뢀터 λ„μž…

hook μ’…λ₯˜

β€’
μƒνƒœ κ΄€λ¦¬μš© hook
β€’
라이프사이클 κ΄€λ¦¬μš© hook
β€’
기타 hook

μƒνƒœκ΄€λ¦¬ hook

이름
μ„€λͺ…
useState()
μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ κΈ°λ³Έ hookμž…λ‹ˆλ‹€.
useReducer()
μƒνƒœλ₯Ό λ³΅μž‘ν•˜κ²Œ κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ hookμž…λ‹ˆλ‹€.
useContext()
λΆ€λͺ¨ μ»΄ν¬λ„ŒνŠΈμ˜ μƒνƒœλ₯Ό μžμ‹ μ»΄ν¬λ„ŒνŠΈμ—μ„œ κ³΅μœ ν•˜κΈ° μœ„ν•œ hookμž…λ‹ˆλ‹€.

라이프사이클 hook

이름
μ„€λͺ…
useEffect()
μ»΄ν¬λ„ŒνŠΈμ˜ 라이프사이클 이벀트λ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•œ hookμž…λ‹ˆλ‹€.
useLayoutEffect()
μ»΄ν¬λ„ŒνŠΈμ˜ λ ˆμ΄μ•„μ›ƒ 이벀트λ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•œ hookμž…λ‹ˆλ‹€.
useRef()
μ»΄ν¬λ„ŒνŠΈμ˜ DOM μš”μ†Œλ‚˜ λ¦¬μ•‘νŠΈ ν›…μ˜ μƒνƒœλ₯Ό μ°Έμ‘°ν•˜κΈ° μœ„ν•œ hookμž…λ‹ˆλ‹€.

기타 hook

이름
μ„€λͺ…
useMemo()
μ»΄ν¬λ„ŒνŠΈκ°€ λ Œλ”λ§λ  λ•Œλ§ˆλ‹€ 값을 κ³„μ‚°ν•˜μ§€ μ•Šκ³ , 이전에 κ³„μ‚°ν•œ 값을 μž¬μ‚¬μš©ν•˜κΈ° μœ„ν•œ hookμž…λ‹ˆλ‹€.
useCallback()
μ»΄ν¬λ„ŒνŠΈκ°€ λ Œλ”λ§λ  λ•Œλ§ˆλ‹€ ν•¨μˆ˜λ₯Ό μž¬μ‹€ν–‰ν•˜μ§€ μ•Šκ³ , 이전에 μ‹€ν–‰ν•œ ν•¨μˆ˜λ₯Ό μž¬μ‚¬μš©ν•˜κΈ° μœ„ν•œ hookμž…λ‹ˆλ‹€.
useImperativeHandle()
μ»΄ν¬λ„ŒνŠΈμ˜ μƒνƒœλ‚˜ 라이프사이클을 imperativeν•˜κ²Œ κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ hookμž…λ‹ˆλ‹€.

hook 이 λ„μž…λœ 이유

λ¦¬μ•‘νŠΈ 16.8 이전 λ²„μ „μ—μ„œλŠ” ν΄λž˜μŠ€ν˜• μ»΄ν¬λ„ŒνŠΈμ—μ„œλ§Œ μƒνƒœκ΄€λ¦¬ 및 라이프사이클 λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
즉, ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈμ—μ„œλŠ” μƒνƒœκ΄€λ¦¬ 및 라이프사이클 λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•  방법이 μ—†μ—ˆμŠ΅λ‹ˆλ‹€.
ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈμ—μ„œ μƒνƒœ 및 라이프사이클 κ΄€λ ¨ λ‘œμ§μ„ 더 κ°„νŽΈν•˜κ²Œ μž‘μ„±ν•˜κ³  μž¬μ‚¬μš© κ°€λŠ₯ν•œ μ½”λ“œλ₯Ό μ‰½κ²Œ λ§Œλ“€κΈ° μœ„ν•¨μž…λ‹ˆλ‹€. μ΄μ „μ—λŠ” ν΄λž˜μŠ€ν˜• μ»΄ν¬λ„ŒνŠΈμ—μ„œλ§Œ μƒνƒœ(state) 및 라이프사이클 κ΄€λ ¨ λ‘œμ§μ„ λ‹€λ£° 수 μžˆμ—ˆμ§€λ§Œ, Hooksκ°€ λ„μž…λ˜λ©΄μ„œ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈμ—μ„œλ„ μ΄λŸ¬ν•œ κΈ°λŠ₯듀을 μ‚¬μš©ν•  수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

hook μž₯점

hook을 μ‚¬μš©ν•˜λ©΄ λ‹€μŒκ³Ό 같은 μž₯점이 μžˆμŠ΅λ‹ˆλ‹€.
β€’
λ¦¬μ•‘νŠΈ μ»΄ν¬λ„ŒνŠΈμ˜ μ½”λ“œλ₯Ό 더 κ°„κ²°ν•˜κ³  가독성 있게 μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
β€’
μƒνƒœ 관리와 라이프사이클 관리λ₯Ό 더 μ‰½κ²Œ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
β€’
λ¦¬μ•‘νŠΈ μ»΄ν¬λ„ŒνŠΈλ₯Ό 더 μœ μ—°ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
hook은 λ¦¬μ•‘νŠΈ μ»΄ν¬λ„ŒνŠΈ κ°œλ°œμ„ 보닀 νŽΈλ¦¬ν•˜κ³  효율적으둜 ν•  수 μžˆλ„λ‘ ν•΄μ£ΌλŠ” μœ μš©ν•œ κΈ°λŠ₯μž…λ‹ˆλ‹€.

클래슀 μ»΄ν¬λ„ŒνŠΈμ˜ 라이프사이클 λ©”μ†Œλ“œ vs hooks

Class Component Lifecycle Method
Hooks
componentDidMount
useEffect(() => {}, [])
componentDidUpdate
useEffect(() => {}, [/* dependency array */])
componentWillUnmount
useEffect(() => { return () => {} }, [])
getDerivedStateFromProps
useStateλ₯Ό μ‚¬μš©ν•˜μ—¬ μƒνƒœ μ—…λ°μ΄νŠΈ`
shouldComponentUpdate
μ΅œμ ν™” 기술 μ‚¬μš© (예: React.memo)
getSnapshotBeforeUpdate
useEffect λ˜λŠ” useLayoutEffect 등을 μ‚¬μš©ν•˜μ—¬ DOM μƒνƒœ 처리`

componentDidMount

β€’
Class Component
componentDidMount() { // μ»΄ν¬λ„ŒνŠΈκ°€ 마운트될 λ•Œ μ‹€ν–‰λ˜λŠ” μ½”λ“œ }
JavaScript
볡사
β€’
Functional Component (hook)
useEffect(() => { // μ»΄ν¬λ„ŒνŠΈκ°€ 마운트될 λ•Œ μ‹€ν–‰λ˜λŠ” μ½”λ“œ }, []);
JavaScript
볡사

componentDidUpdate

β€’
Class Component
componentDidUpdate(prevProps, prevState) { // μ»΄ν¬λ„ŒνŠΈκ°€ μ—…λ°μ΄νŠΈλ  λ•Œ μ‹€ν–‰λ˜λŠ” μ½”λ“œ }
JavaScript
볡사
β€’
Functional Component (hook)
useEffect(() => { // μ»΄ν¬λ„ŒνŠΈκ°€ μ—…λ°μ΄νŠΈλ  λ•Œ μ‹€ν–‰λ˜λŠ” μ½”λ“œ }, [/* dependency array */]);
JavaScript
볡사

componentWillUnmount

β€’
Class Component
componentWillUnmount() { // μ»΄ν¬λ„ŒνŠΈκ°€ μ–Έλ§ˆμš΄νŠΈλ  λ•Œ μ‹€ν–‰λ˜λŠ” μ½”λ“œ }
JavaScript
볡사
β€’
Functional Component (hook)
useEffect(() => { return () => { // μ»΄ν¬λ„ŒνŠΈκ°€ μ–Έλ§ˆμš΄νŠΈλ  λ•Œ μ‹€ν–‰λ˜λŠ” μ½”λ“œ }; }, []);
JavaScript
볡사

getDerivedStateFromProps

β€’
Class Component
static getDerivedStateFromProps(nextProps, nextState) { // propsλ‘œλΆ€ν„° νŒŒμƒλœ μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈ return null; }
JavaScript
볡사
β€’
Functional Component (hook)
useState()
JavaScript
볡사

shouldComponentUpdate

β€’
Class Component
shouldComponentUpdate(nextProps, nextState) { // μ»΄ν¬λ„ŒνŠΈ μ—…λ°μ΄νŠΈ μ—¬λΆ€λ₯Ό κ²°μ • return true; // λ˜λŠ” false }
JavaScript
볡사
β€’
Functional Component (hook)
React.memo λ₯Ό ν†΅ν•œ μ΅œμ ν™”
JavaScript
볡사

getSnapshotBeforeUpdate

β€’
Class Component
getSnapshotBeforeUpdate(prevProps, prevState) { // μ»΄ν¬λ„ŒνŠΈ μ—…λ°μ΄νŠΈ 전에 DOM μƒνƒœλ₯Ό 캑처 return null; }
JavaScript
볡사
β€’
Functional Component (hook)
useEffect와 useLayoutEffect 등을 ν™œμš©ν•˜μ—¬ DOM μƒνƒœλ₯Ό μ²˜λ¦¬ν•©λ‹ˆλ‹€.
JavaScript
볡사

μ™œ useEffect κ°€ 2번 ν˜ΈμΆœλ˜λ‚˜μš”?

StrictMode 인 경우, useEffect() 에 2번 ν˜ΈμΆœλ˜λŠ” ν˜„μƒμ΄ μžˆμŠ΅λ‹ˆλ‹€.
StrictModeμ—μ„œ useEffectκ°€ 두 번 ν˜ΈμΆœλ˜λŠ” 것을 λ§‰μœΌλ €λ©΄, ν΄λ‘œμ €λ₯Ό μ‚¬μš©ν•˜μ—¬ μ»΄ν¬λ„ŒνŠΈμ˜ stateλ‚˜ propsλ₯Ό μ°Έμ‘°ν•˜μ§€ μ•Šλ„λ‘ ν•΄μ•Ό ν•©λ‹ˆλ‹€.

ν•΄κ²° 방법

StrictMode 제거
ν΄λ‘œμ € μ‚¬μš©
β€’
useEffect κ°€ 2번 ν˜ΈμΆœλ˜λŠ” ν˜„μƒμ„ ν•΄κ²°ν•˜λ €λ©΄ μ•„λž˜ 2가지 방법 쀑 1가지λ₯Ό μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€. λ‹€λ§Œ, 근본적인 ν•΄κ²° 방법은 ν΄λ‘œμ €λ₯Ό μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

이유 StrictMode

StrictModeλŠ” React의 μ½”λ“œ μŠ€νƒ€μΌμ„ 보닀 μ—„κ²©ν•˜κ²Œ κ²€μ‚¬ν•˜λŠ” λͺ¨λ“œμž…λ‹ˆλ‹€. StrictModeλ₯Ό μ‚¬μš©ν•˜λ©΄ λ‹€μŒκ³Ό 같은 이점이 μžˆμŠ΅λ‹ˆλ‹€.
β€’
μ½”λ“œμ˜ 일관성을 높이고 버그λ₯Ό 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€.
β€’
μ½”λ“œμ˜ μ„±λŠ₯을 ν–₯μƒμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.
β€’
μ½”λ“œμ˜ ν…ŒμŠ€νŠΈ κ°€λŠ₯성을 ν–₯μƒμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.
StrictModeλŠ” λ‹€μŒκ³Ό 같은 검사λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.
β€’
λ Œλ”λ§ λ‹¨κ³„μ˜ λΆ€μž‘μš© 검사
β€’
클래슀 μ»΄ν¬λ„ŒνŠΈμ˜ μ•ˆμ „ν•˜μ§€ μ•Šμ€ 생λͺ…μ£ΌκΈ° λ©”μ„œλ“œ 검사
β€’
useState(),Β useEffect(),Β useContext()Β ν›…μ˜ λΆ€μ μ ˆν•œ μ‚¬μš© 검사
β€’
λ¦¬μ•‘νŠΈ API의 잘λͺ»λœ μ‚¬μš© 검사
StrictModeλ₯Ό μ‚¬μš©ν•˜λ €λ©΄ λ‹€μŒκ³Ό 같이 μ½”λ“œμ— React.StrictMode μ»΄ν¬λ„ŒνŠΈλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
import React, { StrictMode } from "react"; function App() { return ( <StrictMode> // StrictMode μ•„λž˜μ— μžˆλŠ” μ»΄ν¬λ„ŒνŠΈκ°€ κ²€μ‚¬λ©λ‹ˆλ‹€. <MyComponent /> </StrictMode> ); }
JavaScript
볡사

ν˜„μƒ 확인

const App = () => { const [count, setCount] = useState(0); useEffect(() => { // 타이머λ₯Ό μ„€μ •ν•©λ‹ˆλ‹€. setInterval(() => setCount(count + 1), 1000); }, [count]); return ( <div> <h1>ν˜„μž¬ 카운트: {count}</h1> </div> ); };
JavaScript
볡사
이 μ½”λ“œμ—μ„œλŠ” incrementCount ν•¨μˆ˜λ₯Ό ν΄λ‘œμ €λ‘œ μƒμ„±ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λŒ€μ‹ , setInterval() ν•¨μˆ˜μ˜ 첫 번째 인자둜 μ „λ‹¬λœ 콜백 ν•¨μˆ˜μ—μ„œ count λ³€μˆ˜μ— 직접 μ ‘κ·Όν•©λ‹ˆλ‹€. λ”°λΌμ„œ, useEffect()κ°€ 두 번 호좜될 λ•Œλ§ˆλ‹€ count λ³€μˆ˜μ˜ 값이 λ³€κ²½λ©λ‹ˆλ‹€.
( ν˜„μƒ ν•΄κ²° λ‚΄μš© μˆ˜μ •μ€‘ )

ν˜„μƒ ν•΄κ²°

ν΄λ‘œμ € μ‚¬μš©
const App = () => { const [count, setCount] = useState(0); useEffect(() => { // ν΄λ‘œμ €λ₯Ό μ»΄ν¬λ„ŒνŠΈμ˜ props둜 μ΄λ™ν•©λ‹ˆλ‹€. const incrementCount = () => setCount(count + 1); // 콜백 ν•¨μˆ˜μ—μ„œ ν΄λ‘œμ €λ₯Ό μ°Έμ‘°ν•©λ‹ˆλ‹€. return () => { clearInterval(incrementCount); }; }, [count]); return ( <div> <h1>ν˜„μž¬ 카운트: {count}</h1> </div> ); };
JavaScript
볡사

ν΄λ‘œμ € (closure) λž€?

ν΄λ‘œμ €λŠ” ν•¨μˆ˜μ™€ κ·Έ ν•¨μˆ˜κ°€ μ„ μ–Έλœ ν™˜κ²½μ„ ν•¨κ»˜ 묢은 κ²ƒμž…λ‹ˆλ‹€. ν΄λ‘œμ €λŠ” ν•¨μˆ˜κ°€ μ„ μ–Έλœ ν™˜κ²½μ˜ λ³€μˆ˜μ— μ ‘κ·Όν•  수 μžˆλŠ” κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€.
function outer() { const inner = () => { // inner ν•¨μˆ˜λŠ” outer ν•¨μˆ˜κ°€ μ„ μ–Έλœ ν™˜κ²½μ˜ count λ³€μˆ˜μ— μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€. const count = 10; return count; }; return inner; } const closure = outer(); const result = closure(); console.log(result); // 10
JavaScript
볡사
μœ„ μ½”λ“œμ—μ„œλŠ” outer() ν•¨μˆ˜μ—μ„œ inner() ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. inner() ν•¨μˆ˜λŠ” outer() ν•¨μˆ˜κ°€ μ„ μ–Έλœ ν™˜κ²½μ˜ count λ³€μˆ˜μ— μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€. closure λ³€μˆ˜λŠ” outer() ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’μΈ inner() ν•¨μˆ˜λ₯Ό μ°Έμ‘°ν•©λ‹ˆλ‹€. result λ³€μˆ˜λŠ” closure() ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜μ—¬ λ°˜ν™˜λœ 값을 μ €μž₯ν•©λ‹ˆλ‹€.
ν΄λ‘œμ €λŠ” λ‹€μŒκ³Ό 같은 μš©λ„λ‘œ μ‚¬μš©λ  수 μžˆμŠ΅λ‹ˆλ‹€.
β€’
ν•¨μˆ˜μ˜ λ‚΄λΆ€ μƒνƒœλ₯Ό μœ μ§€ν•©λ‹ˆλ‹€.
β€’
κ³ μ°¨ ν•¨μˆ˜μ—μ„œ ν•¨μˆ˜μ˜ 인자둜 μ „λ‹¬λœ ν•¨μˆ˜μ˜ ν™˜κ²½μ„ μœ μ§€ν•©λ‹ˆλ‹€.
β€’
콜백 ν•¨μˆ˜μ—μ„œ μ™ΈλΆ€ ν™˜κ²½μ˜ λ³€μˆ˜μ— μ ‘κ·Όν•©λ‹ˆλ‹€.
ν΄λ‘œμ €λ₯Ό μ‚¬μš©ν•  λ•ŒλŠ” λ‹€μŒκ³Ό 같은 주의 사항을 κ³ λ €ν•΄μ•Ό ν•©λ‹ˆλ‹€.
β€’
ν΄λ‘œμ €λŠ” ν•¨μˆ˜κ°€ μ„ μ–Έλœ ν™˜κ²½μ˜ λ³€μˆ˜μ— μ ‘κ·Όν•  수 있기 λ•Œλ¬Έμ—, λ³€μˆ˜μ˜ λ²”μœ„λ₯Ό 잘 이해해야 ν•©λ‹ˆλ‹€.
β€’
ν΄λ‘œμ €λŠ” λ©”λͺ¨λ¦¬ λˆ„μˆ˜μ˜ 원인이 될 수 μžˆμœΌλ―€λ‘œ, μ‚¬μš©μ΄ μ™„λ£Œλœ ν›„μ—λŠ” μ‚­μ œν•΄μ•Ό ν•©λ‹ˆλ‹€.

useState()

λ¦¬μ•‘νŠΈ ν•¨μˆ˜ μ»΄ν¬λ„ŒνŠΈμ—μ„œ μƒνƒœ 관리λ₯Ό ν•  수 μžˆλŠ” ν›…
β€’
μƒνƒœ 값을 μ €μž₯ν•˜κ³ , μƒνƒœ 값이 변경될 λ•Œλ§ˆλ‹€ μ»΄ν¬λ„ŒνŠΈλ₯Ό λ¦¬λ Œλ”λ§ν•©λ‹ˆλ‹€.
β€’
useStateλŠ” 두 개의 인자λ₯Ό λ°›μŠ΅λ‹ˆλ‹€.
β—¦
첫 번째 μΈμžλŠ” μƒνƒœ κ°’μ˜ μ΄ˆκΉƒκ°’μž…λ‹ˆλ‹€.
β—¦
두 번째 μΈμžλŠ” μƒνƒœ 값을 λ³€κ²½ν•˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€.

문법

const [μƒνƒœ, μƒνƒœλ³€κ²½ν•¨μˆ˜] = useState(μ΄ˆκΈ°κ°’);
JavaScript
볡사
function Counter() { const [count, setCount] = useState(0); return ( <div> <button onClick={() => setCount(count + 1)}>+</button> <button onClick={() => setCount(count - 1)}>-</button> <p>{count}</p> </div> ); }
JavaScript
볡사

useEffect()

λ¦¬μ•‘νŠΈ ν•¨μˆ˜ μ»΄ν¬λ„ŒνŠΈμ˜ 생λͺ…주기와 κ΄€λ ¨λœ μž‘μ—…μ„ μˆ˜ν–‰ν•  수 μžˆλŠ” ν›…
β€’
μ»΄ν¬λ„ŒνŠΈκ°€ λ§ˆμš΄νŠΈλ˜κ±°λ‚˜ μ–Έλ§ˆμš΄νŠΈλ  λ•Œ, μƒνƒœ 값이 변경될 λ•Œ λ“± νŠΉμ • μ‹œμ μ— μž‘μ—…μ„ μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
β€’
useEffectλŠ” 두 개의 인자λ₯Ό λ°›μŠ΅λ‹ˆλ‹€.
β—¦
첫 번째 μΈμžλŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€.
β—¦
두 번째 μΈμžλŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•  λ•Œ μ°Έμ‘°ν•΄μ•Ό ν•˜λŠ” μ˜μ‘΄μ„± λ°°μ—΄μž…λ‹ˆλ‹€.

문법

useEffect( () => { // μ»΄ν¬λ„ŒνŠΈκ°€ 마운트 된 이후 μ‹€ν–‰ // μΆ”κ°€μ μœΌλ‘œ, μ˜μ‘΄μ„± 배열에 λ”°λΌμ„œ 싀행됨 return () => { // μ»΄ν¬λ„ŒνŠΈκ°€ μ–Έλ§ˆμš΄νŠΈ 되기 전에 싀행됨 } }, μ˜μ‘΄μ„±λ°°μ—΄ );
JavaScript
볡사
1.
μ˜μ‘΄μ„± 배열을 [ ] 빈 λ°°μ—΄λ‘œ μ§€μ •ν•œ 경우
2.
μ˜μ‘΄μ„± 배열에 λ³€μˆ˜λ₯Ό μ§€μ •ν•œ 경우
3.
μ˜μ‘΄μ„± 배열을 μƒλž΅ν•œ 경우

μ˜μ‘΄μ„± 배열을 [ ] 빈 λ°°μ—΄λ‘œ μ§€μ •ν•œ 경우

μ»΄ν¬λ„ŒνŠΈ λ§ˆμš΄νŠΈμ™€ μ–Έλ§ˆμš΄νŠΈ μ‹œ, useEffect() κ°€ ν•œ 번만 μ‹€ν–‰λ©λ‹ˆλ‹€.
function Counter() { const [count, setCount] = useState(0); useEffect(() => { // μ»΄ν¬λ„ŒνŠΈκ°€ 마운트/μ–Έλ§ˆμš΄νŠΈ μ‹œ ν•œ 번만 μ‹€ν–‰ }, []); return ( <div> <button onClick={() => setCount(count + 1)}>+</button> <button onClick={() => setCount(count - 1)}>-</button> <p>{count}</p> </div> ); }
JavaScript
볡사

μ˜μ‘΄μ„± 배열에 λ³€μˆ˜λ₯Ό μ§€μ •ν•œ 경우

μ˜μ‘΄μ„± λ³€μˆ˜κ°€ 변경될 λ•Œλ§ˆλ‹€ useEffect() κ°€ μ‹€ν–‰λ©λ‹ˆλ‹€.
function Counter() { const [count, setCount] = useState(0); const id = 1; useEffect(() => { // id의 값이 변경될 λ•Œλ§ˆλ‹€ μ‹€ν–‰ }, [id]); return ( <div> <button onClick={() => setCount(count + 1)}>+</button> <button onClick={() => setCount(count - 1)}>-</button> <p>{count}</p> </div> ); }
JavaScript
볡사

μ˜μ‘΄μ„± 배열을 μƒλž΅ν•œ 경우

μ»΄ν¬λ„ŒνŠΈκ°€ 마운트될 λ•Œλ§ˆλ‹€, μƒνƒœκ°€ μ—…λ°μ΄νŠΈλ  λ•Œλ§ˆλ‹€ useEffect() κ°€ μ‹€ν–‰λ©λ‹ˆλ‹€.
function Counter() { const [count, setCount] = useState(0); useEffect(() => { // μ»΄ν¬λ„ŒνŠΈκ°€ 마운트될 λ•Œλ§ˆλ‹€, 그리고 μƒνƒœκ°€ μ—…λ°μ΄νŠΈ 될 λ•Œλ§ˆλ‹€ μ‹€ν–‰ }); return ( <div> <button onClick={() => setCount(count + 1)}>+</button> <button onClick={() => setCount(count - 1)}>-</button> <p>{count}</p> </div> ); }
JavaScript
볡사

μ‹€μŠ΅μ½”λ“œ

β€œTodo List - ν•  일 λͺ©λ‘ UI λ§Œλ“€κΈ°β€

μž‘μ—… 파일

β€’
App.js
β€’
App.css
β€’
components
β—¦
TodoContainer.jsx
β—¦
TodoHeader.jsx
β—¦
TodoInput.jsx
β—¦
TodoList.jsx
β—¦
TodoItem.jsx
β—¦
TodoFooter.jsx

μ»΄ν¬λ„ŒνŠΈ ꡬ쑰

β€’
App.
β—¦
TodoContainer
β–ͺ
TodoHeader
β–ͺ
TodoInput
β–ͺ
TodoList
β€’
TodoItem
β–ͺ
TodoFooter

μ½”λ“œ μž‘μ—…

β€’
components
β—¦
TodoContainer.jsx
β—¦
TodoHeader.jsx
β—¦
TodoInput.jsx
β—¦
TodoList.jsx
β—¦
TodoItem.jsx
β—¦
TodoFooter.jsx
β€’
App.css
β€’
App.js

TodoContainer.jsx

import React, { useEffect, useState } from 'react' import TodoHeader from './TodoHeader' import TodoInput from './TodoInput' import TodoList from './TodoList' import TodoFooter from './TodoFooter' const TodoContainer = () => { // βœ… state const [todoList, setTodoList] = useState([]); const [input, setInput] = useState('') // ❓ hook useEffect(() => { // βœ… Mount, Update // 할일 λͺ©λ‘ μš”μ²­ [GET] fetch('http://192.168.30.119:8080/todos') .then( ( response ) => response.json() ) .then( ( data ) => setTodoList(data) ) .catch( (error) => console.log(error) ); console.log('[GET] - /todos - 할일λͺ©λ‘ μš”μ²­'); // βœ… UnMount return () => { } }, []) // 할일 등둝 const onSubmit = () => { console.log('할일 : ' + input); if( input == '' ) { // alert('ν•  일을 μž…λ ₯ν•΄μ£Όμ„Έμš”') // return } const data = { name: input == '' ? '제λͺ©μ—†μŒ' : input, // 할일 제λͺ© status: 0, // μ™„λ£Œ μ—¬λΆ€(λ―Έμ™„λ£Œ-0,μ™„λ£Œ-1) }; const init = { method : 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data) }; // 할일 등둝 [POST] // ➑ μƒˆλ‘œ λ“±λ‘λœ 할일 데이터 fetch('http://192.168.30.119:8080/todos',init) .then( ( response ) => response.json() ) .then( ( data ) => setTodoList([data, ...todoList]) ) .catch( (error) => console.log(error) ); setInput('') } // 할일 μž…λ ₯ 이벀트 const onChange = (e) => { setInput(e.target.value); } // 할일 μ™„λ£Œ μ—¬λΆ€ μˆ˜μ • const onToggle = (todo) => { console.log('할일 μ™„λ£Œ μ—¬λΆ€ 처리'); console.log(todo); // PUT μš”μ²­ const data = { no: todo.no, name: todo.name, status: todo.status ? 0 : 1, } const init = { method: 'PUT', headers: { 'Content-Type' : 'application/json', }, body: JSON.stringify(data), } // 할일 μˆ˜μ • [PUT] // ➑ 'μˆ˜μ • μ™„λ£Œ λ©”μ‹œμ§€' fetch('http://192.168.30.119:8080/todos',init) .then( ( response ) => response.text() ) .then( ( data ) => console.log(data) ) .catch( (error) => console.log(error) ); // 할일 μ™„λ£Œ 처리 ν›„, // ➑ ν΄λ¦­ν•œ ν• μΌμ˜ μ™„λ£Œ μ—¬λΆ€λ₯Ό toggle // let updatedTodoList = todoList.map((item) => { // return item.no === todo.no ? {...item, status: !item.status} : item; // }); // }) // ➑ μ™„λ£Œλœ 할일은 μ•„λž˜μͺ½ μ •λ ¬ // let sortedTodoList = updatedTodoList.sort((a, b) => { // μ •λ ¬ κΈ°μ€€ // 1. μ™„λ£Œ μ—¬λΆ€ - 0, 1 μ˜€λ¦„μ°¨μˆœ // 2. 할일 번호 λ‚΄λ¦Όμ°¨μˆœ // return a.status - b.status == 0 ? b.no - a.no : a.status - b.status; // }) const sortedTodoList = todoList.map((item) => item.no === todo.no ? {...item, status: !item.status} : item) .sort((a, b) => a.status - b.status == 0 ? b.no - a.no : a.status - b.status) setTodoList(sortedTodoList) }; // 할일 μ‚­μ œ const onDelete = (no) => { // DELETE μš”μ²­ const init = { method: 'DELETE', }; // 할일 μ‚­μ œ [DELETE] // ➑ 'μ‚­μ œ μ™„λ£Œ λ©”μ‹œμ§€' fetch(`http://192.168.30.119:8080/todos/${no}`,init) .then( ( response ) => response.text() ) .then( ( data ) => console.log(data) ) .catch( (error) => console.log(error) ); // μ‚­μ œλœ 할일 ν•­λͺ© 제거 const updatedTodoList = todoList.filter( (todo) => todo.no != no ) setTodoList( updatedTodoList ) } // 전체 μ‚­μ œ const onDeleteAll = () => { // [DELETE] /todos/-1 // DELETE μš”μ²­ const init = { method: 'DELETE', }; // 전체 할일 μ‚­μ œ [DELETE] // ➑ 'μ‚­μ œ μ™„λ£Œ λ©”μ‹œμ§€' fetch(`http://192.168.30.119:8080/todos/-1`,init) .then( ( response ) => response.text() ) .then( ( data ) => console.log(data) ) .catch( (error) => console.log(error) ); // μ‚­μ œλœ 할일 ν•­λͺ© 제거 setTodoList( [] ) } // 전체 μ™„λ£Œ const onCompleteAll = () => { // PUT μš”μ²­ // μ „μ²΄μ™„λ£Œ // /todos : data : { no : -1 } const data = { no: -1, } const init = { method: 'PUT', headers: { 'Content-Type' : 'application/json', }, body: JSON.stringify(data), } // 할일 전체 μ™„λ£Œ [PUT] // ➑ 'μˆ˜μ • μ™„λ£Œ λ©”μ‹œμ§€' fetch('http://192.168.30.119:8080/todos',init) .then( ( response ) => response.text() ) .then( ( data ) => console.log(data) ) .catch( (error) => console.log(error) ); const sortedTodoList = todoList.map((item) => ({ ...item, status: 1 }) ) .sort((a, b) => a.status - b.status == 0 ? b.no - a.no : a.status - b.status) setTodoList(sortedTodoList) } // 할일 μˆ˜μ • const onUpdate = (todo) => { console.log('할일 μˆ˜μ • 처리'); console.log(todo); // PUT μš”μ²­ const data = { no: todo.no, name: todo.name, status: todo.status, } const init = { method: 'PUT', headers: { 'Content-Type' : 'application/json', }, body: JSON.stringify(data), } // 할일 μˆ˜μ • [PUT] // ➑ 'μˆ˜μ • μ™„λ£Œ λ©”μ‹œμ§€' fetch('http://192.168.30.119:8080/todos',init) .then( ( response ) => response.text() ) .then( ( data ) => console.log(data) ) .catch( (error) => console.log(error) ); const sortedTodoList = todoList.sort((a, b) => a.status - b.status == 0 ? b.no - a.no : a.status - b.status) setTodoList(sortedTodoList) }; return ( <div className="container"> <TodoHeader/> <TodoInput input={input} onChange={onChange} onSubmit={onSubmit}/> <TodoList todoList={todoList} onToggle={onToggle} onDelete={onDelete} onUpdate={onUpdate} /> <TodoFooter onDeleteAll={onDeleteAll} onCompleteAll={onCompleteAll} /> </div> ) } export default TodoContainer
JavaScript
볡사

TodoHeader.jsx

import React from 'react' const TodoHeader = () => { return ( <div className='header'> <h1>To Do List</h1> </div> ) } export default TodoHeader
JavaScript
볡사

TodoInput.jsx

import React, { useState } from 'react' const TodoInput = ({ input, onSubmit, onChange }) => { return ( <div> <form className='form'> <input placeholder='ν•  일 μž…λ ₯' className='input' onChange={onChange} value={input} /> <button type='button' className='btn' onClick={onSubmit} >μΆ”κ°€</button> </form> </div> ) } export default TodoInput
JavaScript
볡사

TodoList.jsx

import React from 'react' import TodoItem from './TodoItem' const TodoList = ( { todoList, onToggle, onDelete, onUpdate } ) => { return ( <ul className="todoList"> {todoList.map( (todo) => ( <TodoItem key={todo.no} todo={todo} onToggle={onToggle} onDelete={onDelete} onUpdate={onUpdate} /> ))} </ul> ) } export default TodoList
JavaScript
볡사

TodoItem.jsx

import React from 'react' const TodoItem = ( { todo, onToggle, onDelete, onUpdate } ) => { let { no, name, status } = todo; status = status == 1 ? true : false; const className = status ? 'todoItem active' : 'todoItem'; return ( <li className={className}> <div className="item"> <input type="checkbox" id={todo.no} checked={status} onChange={ () => onToggle(todo) } /> <label htmlFor={todo.no}></label> {/* 할일 */} <input type="text" id={`name-${todo.no}`} className='input' // value={todo.name} defaultValue={todo.name} /> </div> <div className="item"> <button className='btn btn-sm' onClick={ () => { todo.name = document.getElementById(`name-${todo.no}`).value; onUpdate(todo); }}>βœ…</button> <button className='btn btn-sm' onClick={ () => onDelete(no) }>❌</button> </div> </li> ) } export default TodoItem
JavaScript
볡사

TodoFooter.jsx

import React from 'react' const TodoFooter = ({ onDeleteAll, onCompleteAll }) => { return ( <div className='footer'> <div className="item"> <button className='btn' onClick={ onDeleteAll }>μ „μ²΄μ‚­μ œ</button> </div> <div className="item"> <button className='btn' onClick={ onCompleteAll }>μ „μ²΄μ™„λ£Œ</button> </div> </div> ) } export default TodoFooter
JavaScript
볡사

App.css

.container { width: 480px; min-height: 600px; margin: 100px auto; padding: 20px; border: 2px solid cornflowerblue; border-radius: 10px; box-shadow: 5px 5px 5px rgba(0,0,0,0.3); background-color: rgba(200,200,200,0.1); } .header { text-align: center; font-size: 20px; color: royalblue; } .form { display: flex; justify-content: space-between; margin: 10px 10px 10px 0; } .form .input { width: 80%; height: 30px; } .input::placeholder { color: royalblue; } .input { border: none; border-bottom: 1px solid royalblue; padding-left: 12px; font-size: 18px; outline: none; color: blue; font-weight: bold; background-color: transparent; } .form .btn { width: 15%; } .btn { width: 80px; height: 40px; padding: 0 10px; font-weight: bold; border: none; border-radius: 12px; background-color: cornflowerblue; color: white; cursor: pointer; } .btn.btn-sm { width: auto; height: 40px; padding: 0 12px; } .btn:hover { background-color: royalblue; box-shadow: 2px 2px 5px rgba(0,0,0,0.5); } .btn:active { box-shadow: none; } .todoList { height: 500px; overflow-y: auto; } ul { margin: 0; padding: 0; } .todoItem { list-style-type: none; display: flex; justify-content: space-between; align-items: center; margin: 10px 10px 10px 0; padding: 20px; border: 2px solid cornflowerblue; border-radius: 12px; color: royalblue; font-size: 18px; box-shadow: 5px 5px 5px rgba(0,0,0,0.3); } .todoItem.active { background-color: royalblue; color: white; } .todoItem.active .btn:hover { background-color: blue; } .todoItem .item { display: flex; align-items: center; gap: 14px; } /* μ²΄ν¬λ°•μŠ€ */ .todoItem input[type='checkbox'] { display: none; } .todoItem label { display: inline-block; width: 20px; height: 20px; border: 3px dashed royalblue; border-radius: 50%; } .todoItem input:checked ~ label { background-color: cornflowerblue; border: 3px solid blue; } .todoItem input:checked ~ input { text-decoration: line-through; } .footer { display: flex; justify-content: space-between; align-items: center; margin: 10px 10px 10px 0; } /* μŠ€ν¬λ‘€λ°” */ ::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); background-color: transparent; border-radius: 10px; } ::-webkit-scrollbar { width: 10px; background-color: transparent; } ::-webkit-scrollbar-thumb { border-radius: 10px; background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.44, rgb(122,153,217)), color-stop(0.72, rgb(73,125,189)), color-stop(0.86, rgb(28,58,148))); } /* λ°°κ²½ */ body { background: linear-gradient( -45deg, #ffffff, #ffe4e9, #fcc8e2, #f6baed, #efb0ff, #d3a2ff, #8eb8ff, #91efff, #d5f9ff ); background-size: 400% 400%; animation: gradient 9s ease infinite; height: 100vh; } @keyframes gradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
CSS
볡사

App.js

import './App.css'; import TodoContainer from './components/TodoContainer'; function App() { return ( <> <TodoContainer/> </> ); } export default App;
JavaScript
볡사