๐Ÿ“˜React ๊ธฐ์ดˆ ์ •๋ฆฌ 3 โ€” Hooks ์™„์ „ ์ •๋ณต

@leekh8 ยท May 05, 2023 ยท 16 min read

React Hooks ์™„์ „ ์ •๋ณต

React 16.8์—์„œ ๋„์ž…๋œ Hooks๋Š” ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ๋„ State์™€ ์ƒ๋ช…์ฃผ๊ธฐ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€ ํ˜์‹ ์ ์ธ ๋ณ€ํ™”๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ์ฃผ์š” Hooks ์ „๋ถ€๋ฅผ ์‹ค์ „ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ๊นŠ๊ฒŒ ์‚ดํŽด๋ณธ๋‹ค.


1. Hooks๋ž€? ์™œ ๋“ฑ์žฅํ–ˆ๋‚˜?

Hooks๊ฐ€ ๋“ฑ์žฅํ•˜๊ธฐ ์ „, React๋Š” ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ์™€ ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์—ˆ๋‹ค. ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹จ์ˆœํ•˜๊ณ  ๊ฐ€๋ณ์ง€๋งŒ, State ๊ด€๋ฆฌ๋‚˜ ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์—ˆ๋‹ค. ๋ณต์žกํ•œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ๋ฐ˜๋“œ์‹œ ํด๋ž˜์Šค ์ปดํฌ๋„ŒํŠธ๋ฅผ ์จ์•ผ ํ–ˆ๋‹ค.

ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ์˜ ๋ฌธ์ œ์ 

// ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ โ€” ๋ณต์žกํ•˜๊ณ  ์žฅํ™ฉํ•˜๋‹ค
class UserProfile extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      user: null,
      loading: true,
      error: null,
    }
    this.handleClick = this.handleClick.bind(this)  // this ๋ฐ”์ธ๋”ฉ ํ•„์ˆ˜
  }

  componentDidMount() {
    this.fetchUser()
  }

  componentDidUpdate(prevProps) {
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser()
    }
  }

  componentWillUnmount() {
    // ํด๋ฆฐ์—… ๋กœ์ง
  }

  fetchUser() {
    fetch(`/api/users/${this.props.userId}`)
      .then(res => res.json())
      .then(user => this.setState({ user, loading: false }))
      .catch(error => this.setState({ error, loading: false }))
  }

  handleClick() {
    // this ๋ฐ”์ธ๋”ฉ ์—†์œผ๋ฉด ์—๋Ÿฌ
  }

  render() {
    const { user, loading, error } = this.state
    if (loading) return <p>๋กœ๋”ฉ ์ค‘...</p>
    if (error) return <p>์—๋Ÿฌ ๋ฐœ์ƒ</p>
    return <div>{user.name}</div>
  }
}

ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ์˜ ์ฃผ์š” ๋ฌธ์ œ:

  • this ๋ฐ”์ธ๋”ฉ ๋ฌธ์ œ๋กœ ์ธํ•œ ๋ฒ„๊ทธ
  • ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์„œ๋“œ์— ๊ด€๋ จ ์—†๋Š” ๋กœ์ง์ด ์„ž์ž„ (componentDidMount์— ์—ฌ๋Ÿฌ API ํ˜ธ์ถœ)
  • ๋กœ์ง ์žฌ์‚ฌ์šฉ์ด ์–ด๋ ค์›€ (HOC, render props ํŒจํ„ด์ด ๋ณต์žกํ•จ)
  • ํด๋ž˜์Šค ์ž์ฒด๊ฐ€ JavaScript ์ดˆ๋ณด์ž์—๊ฒŒ ๋‚ฏ์„  ๊ฐœ๋…

Hooks๋กœ ๋ณ€ํ™˜ํ•˜๋ฉด

// ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ + Hooks โ€” ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•˜๋‹ค
function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)

  useEffect(() => {
    setLoading(true)
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(user => { setUser(user); setLoading(false) })
      .catch(error => { setError(error); setLoading(false) })
  }, [userId])  // userId๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ์žฌ์‹คํ–‰

  if (loading) return <p>๋กœ๋”ฉ ์ค‘...</p>
  if (error) return <p>์—๋Ÿฌ ๋ฐœ์ƒ</p>
  return <div>{user.name}</div>
}

ํ›จ์”ฌ ์งง๊ณ  ์ง๊ด€์ ์ด๋‹ค. ์ด๊ฒŒ Hooks์˜ ํž˜์ด๋‹ค.


2. useState ์‹ฌํ™”

useState๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ Hook์ด์ง€๋งŒ ์•Œ์•„์•ผ ํ•  ์„ธ๋ถ€ ์‚ฌํ•ญ์ด ์žˆ๋‹ค.

์ง€์—ฐ ์ดˆ๊ธฐํ™”(Lazy Initialization)

์ดˆ๊ธฐ๊ฐ’ ๊ณ„์‚ฐ์ด ๋ฌด๊ฑฐ์šธ ๋•Œ, ๋งค ๋ Œ๋”๋ง๋งˆ๋‹ค ๊ณ„์‚ฐํ•˜์ง€ ์•Š๋„๋ก ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

// ๋งค ๋ Œ๋”๋ง๋งˆ๋‹ค ์‹คํ–‰๋จ (๋ถˆํ•„์š”ํ•œ ๊ณ„์‚ฐ)
const [data, setData] = useState(heavyCalculation())

// ์ง€์—ฐ ์ดˆ๊ธฐํ™”: ์ตœ์ดˆ ๋ Œ๋”๋ง์—๋งŒ ์‹คํ–‰๋จ
const [data, setData] = useState(() => heavyCalculation())

// ์‹ค์ œ ์˜ˆ์‹œ: localStorage์—์„œ ์ดˆ๊ธฐ๊ฐ’ ์ฝ๊ธฐ
const [theme, setTheme] = useState(() => {
  const saved = localStorage.getItem('theme')
  return saved || 'light'
})

ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ

์ด์ „ State ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์—…๋ฐ์ดํŠธํ•  ๋•Œ๋Š” ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

function Counter() {
  const [count, setCount] = useState(0)

  // ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ: ํ•ญ์ƒ ์ตœ์‹  state ๊ฐ’์„ ๋ณด์žฅ
  const increment = () => setCount(prev => prev + 1)
  const decrement = () => setCount(prev => prev - 1)
  const double = () => setCount(prev => prev * 2)
  const reset = () => setCount(0)

  // ์—ฌ๋Ÿฌ ๋ฒˆ ์—ฐ์† ์—…๋ฐ์ดํŠธ ์‹œ ํ•จ์ˆ˜ํ˜•์ด ์•ˆ์ „
  const incrementThree = () => {
    setCount(prev => prev + 1)
    setCount(prev => prev + 1)
    setCount(prev => prev + 1)
    // ๊ฒฐ๊ณผ: ์ •ํ™•ํžˆ 3 ์ฆ๊ฐ€
  }

  return (
    <div>
      <p>์นด์šดํŠธ: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
      <button onClick={double}>x2</button>
      <button onClick={incrementThree}>+3</button>
      <button onClick={reset}>์ดˆ๊ธฐํ™”</button>
    </div>
  )
}

3. useEffect ์™„์ „ ์ •๋ณต

useEffect๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋œ ํ›„์— ์‹คํ–‰๋˜๋Š” ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค. API ํ˜ธ์ถœ, ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก, DOM ์ง์ ‘ ์กฐ์ž‘ ๋“ฑ์— ์‚ฌ์šฉํ•œ๋‹ค.

์˜์กด์„ฑ ๋ฐฐ์—ด์˜ ์„ธ ๊ฐ€์ง€ ํ˜•ํƒœ

// 1. ์˜์กด์„ฑ ๋ฐฐ์—ด ์—†์Œ: ๋งค ๋ Œ๋”๋ง ํ›„๋งˆ๋‹ค ์‹คํ–‰
useEffect(() => {
  console.log('๋งค๋ฒˆ ์‹คํ–‰')
})

// 2. ๋นˆ ๋ฐฐ์—ด: ์ตœ์ดˆ ๋งˆ์šดํŠธ ์‹œ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰
useEffect(() => {
  console.log('๋งˆ์šดํŠธ ์‹œ ํ•œ ๋ฒˆ๋งŒ')
  fetchInitialData()
}, [])

// 3. ์˜์กด์„ฑ ์ง€์ •: ํ•ด๋‹น ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰
useEffect(() => {
  console.log(`userId๊ฐ€ ${userId}๋กœ ๋ณ€๊ฒฝ๋จ`)
  fetchUserData(userId)
}, [userId])

ํด๋ฆฐ์—…(Cleanup) ํ•จ์ˆ˜

useEffect๋Š” ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋˜๊ฑฐ๋‚˜ ๋‹ค์Œ effect๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ํ˜ธ์ถœ๋œ๋‹ค.

function ChatRoom({ roomId }) {
  useEffect(() => {
    // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
    const socket = connectToRoom(roomId)
    socket.on('message', handleMessage)

    console.log(`๋ฐฉ ${roomId}์— ์—ฐ๊ฒฐ๋จ`)

    // ํด๋ฆฐ์—…: ์–ธ๋งˆ์šดํŠธ๋˜๊ฑฐ๋‚˜ roomId๊ฐ€ ๋ฐ”๋€Œ๊ธฐ ์ „์— ์‹คํ–‰
    return () => {
      socket.off('message', handleMessage)
      socket.disconnect()
      console.log(`๋ฐฉ ${roomId}์—์„œ ์—ฐ๊ฒฐ ํ•ด์ œ`)
    }
  }, [roomId])

  return <div>์ฑ„ํŒ…๋ฐฉ: {roomId}</div>
}

ํด๋ฆฐ์—…์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ:

  • ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ (addEventListener โ†’ removeEventListener)
  • ํƒ€์ด๋จธ (setInterval โ†’ clearInterval)
  • ์†Œ์ผ“ ์—ฐ๊ฒฐ ํ•ด์ œ
  • ๋น„๋™๊ธฐ ์š”์ฒญ ์ทจ์†Œ (AbortController)

๋น„๋™๊ธฐ ํ•จ์ˆ˜์™€ useEffect

useEffect์˜ ์ฝœ๋ฐฑ์€ ์ง์ ‘ async๋กœ ๋งŒ๋“ค ์ˆ˜ ์—†๋‹ค. ๋‚ด๋ถ€์— async ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•ด์„œ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค.

// ์ž˜๋ชป๋œ ์ฝ”๋“œ (๊ฒฝ๊ณ  ๋ฐœ์ƒ)
useEffect(async () => {
  const data = await fetchData()
  setData(data)
}, [])

// ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ• 1: ๋‚ด๋ถ€์— async ํ•จ์ˆ˜ ์ •์˜
useEffect(() => {
  const fetchUser = async () => {
    try {
      const response = await fetch(`/api/users/${userId}`)
      const data = await response.json()
      setUser(data)
    } catch (error) {
      setError(error.message)
    } finally {
      setLoading(false)
    }
  }

  fetchUser()
}, [userId])

// ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ• 2: AbortController๋กœ ์š”์ฒญ ์ทจ์†Œ ์ฒ˜๋ฆฌ
useEffect(() => {
  const controller = new AbortController()

  const fetchUser = async () => {
    try {
      const response = await fetch(`/api/users/${userId}`, {
        signal: controller.signal,
      })
      const data = await response.json()
      setUser(data)
    } catch (error) {
      if (error.name !== 'AbortError') {
        setError(error.message)
      }
    }
  }

  fetchUser()

  return () => controller.abort()  // ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ์š”์ฒญ ์ทจ์†Œ
}, [userId])

๋ฌดํ•œ ๋ฃจํ”„ ์ฃผ์˜

useEffect ์•ˆ์—์„œ State๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ , ๊ทธ State๊ฐ€ ์˜์กด์„ฑ ๋ฐฐ์—ด์— ์žˆ์œผ๋ฉด ๋ฌดํ•œ ๋ฃจํ”„๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

// ๋ฌดํ•œ ๋ฃจํ”„ ๋ฐœ์ƒ!
const [count, setCount] = useState(0)
useEffect(() => {
  setCount(count + 1)  // count๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง โ†’ ๋‹ค์‹œ effect ์‹คํ–‰ โ†’ ๋ฌดํ•œ๋ฐ˜๋ณต
}, [count])

// ํ•ด๊ฒฐ: ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋กœ ์˜์กด์„ฑ ์ œ๊ฑฐ
useEffect(() => {
  const timer = setInterval(() => {
    setCount(prev => prev + 1)  // count ์—†์ด๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์ฆ๊ฐ€
  }, 1000)
  return () => clearInterval(timer)
}, [])  // count๊ฐ€ ์˜์กด์„ฑ ๋ฐฐ์—ด์— ์—†์–ด๋„ ๋จ

4. useRef: DOM ์ฐธ์กฐ์™€ ๊ฐ’ ์œ ์ง€

useRef๋Š” ๋‘ ๊ฐ€์ง€ ์šฉ๋„๋กœ ์‚ฌ์šฉํ•œ๋‹ค: DOM ์—˜๋ฆฌ๋จผํŠธ ์ง์ ‘ ์ ‘๊ทผ, ๋ฆฌ๋ Œ๋”๋ง ์—†์ด ๊ฐ’ ์œ ์ง€.

DOM ์ฐธ์กฐ

function TextInputWithFocus() {
  const inputRef = useRef(null)

  const focusInput = () => {
    inputRef.current.focus()  // DOM ์—˜๋ฆฌ๋จผํŠธ์— ์ง์ ‘ ์ ‘๊ทผ
  }

  const clearInput = () => {
    inputRef.current.value = ''
    inputRef.current.focus()
  }

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="์—ฌ๊ธฐ์— ์ž…๋ ฅ..." />
      <button onClick={focusInput}>ํฌ์ปค์Šค</button>
      <button onClick={clearInput}>์ง€์šฐ๊ธฐ</button>
    </div>
  )
}

๋ฆฌ๋ Œ๋”๋ง ์—†์ด ๊ฐ’ ์œ ์ง€

useRef๋Š” .current ๊ฐ’์ด ๋ณ€ํ•ด๋„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋งํ•˜์ง€ ์•Š๋Š”๋‹ค. ํƒ€์ด๋จธ ID๋‚˜ ์ด์ „ props ๊ฐ’์„ ์ €์žฅํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ค.

function StopWatch() {
  const [time, setTime] = useState(0)
  const [isRunning, setIsRunning] = useState(false)
  const intervalRef = useRef(null)  // ํƒ€์ด๋จธ ID ์ €์žฅ (๋ฆฌ๋ Œ๋”๋ง ๋ถˆํ•„์š”)

  const start = () => {
    if (isRunning) return
    setIsRunning(true)
    intervalRef.current = setInterval(() => {
      setTime(prev => prev + 1)
    }, 1000)
  }

  const stop = () => {
    clearInterval(intervalRef.current)
    setIsRunning(false)
  }

  const reset = () => {
    clearInterval(intervalRef.current)
    setIsRunning(false)
    setTime(0)
  }

  return (
    <div>
      <p>{time}์ดˆ</p>
      <button onClick={start} disabled={isRunning}>์‹œ์ž‘</button>
      <button onClick={stop} disabled={!isRunning}>์ •์ง€</button>
      <button onClick={reset}>๋ฆฌ์…‹</button>
    </div>
  )
}

์ด์ „ ๊ฐ’ ์ €์žฅ (previous value)

function usePrevious(value) {
  const prevRef = useRef()

  useEffect(() => {
    prevRef.current = value  // ๋ Œ๋”๋ง ํ›„ ์ด์ „ ๊ฐ’ ์ €์žฅ
  })

  return prevRef.current  // ํ˜„์žฌ ๋ Œ๋”๋ง์—์„œ๋Š” ์ด์ „ ๊ฐ’ ๋ฐ˜ํ™˜
}

function PriceTracker({ price }) {
  const prevPrice = usePrevious(price)

  const diff = price - (prevPrice || price)
  const direction = diff > 0 ? 'โ–ฒ' : diff < 0 ? 'โ–ผ' : 'โ€”'
  const color = diff > 0 ? 'red' : diff < 0 ? 'blue' : 'black'

  return (
    <div>
      <span>ํ˜„์žฌ ๊ฐ€๊ฒฉ: {price.toLocaleString()}์›</span>
      <span style={{ color }}>
        {direction} {Math.abs(diff).toLocaleString()}์›
      </span>
    </div>
  )
}

5. useMemo: ๊ณ„์‚ฐ ๊ฒฐ๊ณผ ์บ์‹ฑ

useMemo๋Š” ๋ณต์žกํ•œ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ํ•œ๋‹ค. ์˜์กด์„ฑ ๊ฐ’์ด ๋ฐ”๋€Œ์ง€ ์•Š์œผ๋ฉด ์ด์ „ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์žฌ์‚ฌ์šฉํ•œ๋‹ค.

import { useState, useMemo } from 'react'

function ProductList({ products, searchQuery, category }) {
  // ํ•„ํ„ฐ๋ง + ์ •๋ ฌ โ€” ๋น„์šฉ์ด ํฐ ์—ฐ์‚ฐ
  const filteredProducts = useMemo(() => {
    console.log('ํ•„ํ„ฐ๋ง ์—ฐ์‚ฐ ์‹คํ–‰')
    return products
      .filter(p => {
        const matchesSearch = p.name.toLowerCase().includes(searchQuery.toLowerCase())
        const matchesCategory = category === 'all' || p.category === category
        return matchesSearch && matchesCategory
      })
      .sort((a, b) => a.price - b.price)
  }, [products, searchQuery, category])  // ์ด ์„ธ ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ๋งŒ ์žฌ๊ณ„์‚ฐ

  const totalPrice = useMemo(() => {
    return filteredProducts.reduce((sum, p) => sum + p.price, 0)
  }, [filteredProducts])

  return (
    <div>
      <p>์ด {filteredProducts.length}๊ฐœ / ํ•ฉ๊ณ„: {totalPrice.toLocaleString()}์›</p>
      <ul>
        {filteredProducts.map(p => (
          <li key={p.id}>{p.name} - {p.price.toLocaleString()}์›</li>
        ))}
      </ul>
    </div>
  )
}

useMemo ์‚ฌ์šฉ ๊ธฐ์ค€

useMemo๋ฅผ ํ•ญ์ƒ ์“ฐ๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™์ง€๋งŒ, ๊ณผ์šฉํ•˜๋ฉด ์˜คํžˆ๋ ค ์„ฑ๋Šฅ์ด ๋‚˜๋น ์ง„๋‹ค.

์‚ฌ์šฉํ•ด์•ผ ํ•  ๋•Œ:

  • ๋ฆฌ์ŠคํŠธ ํ•„ํ„ฐ๋ง/์ •๋ ฌ์ฒ˜๋Ÿผ ๋ฐฐ์—ด ์—ฐ์‚ฐ์ด ๋ฌด๊ฑฐ์šธ ๋•Œ
  • ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅธ Hook์˜ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉ๋  ๋•Œ
  • React DevTools Profiler๋กœ ์‹ค์ œ ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ํ™•์ธ๋˜์—ˆ์„ ๋•Œ

์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋  ๋•Œ:

  • ๋‹จ์ˆœํ•œ ์‚ฌ์น™์—ฐ์‚ฐ
  • ์งง์€ ๋ฐฐ์—ด(10๊ฐœ ์ดํ•˜) ์ฒ˜๋ฆฌ
  • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž์ฃผ ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š์„ ๋•Œ

6. useCallback: ํ•จ์ˆ˜ ๋ฉ”๋ชจ์ด์ œ์ด์…˜

useCallback์€ ํ•จ์ˆ˜๋ฅผ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ํ•œ๋‹ค. ์˜์กด์„ฑ์ด ๋ฐ”๋€Œ์ง€ ์•Š์œผ๋ฉด ๊ฐ™์€ ํ•จ์ˆ˜ ์ฐธ์กฐ๋ฅผ ์œ ์ง€ํ•œ๋‹ค.

import { useState, useCallback, memo } from 'react'

// React.memo๋กœ ์ตœ์ ํ™”๋œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ
const Button = memo(({ onClick, label }) => {
  console.log(`Button "${label}" ๋ Œ๋”๋ง`)
  return <button onClick={onClick}>{label}</button>
})

function Parent() {
  const [count, setCount] = useState(0)
  const [text, setText] = useState('')

  // useCallback ์—†์ด โ€” ๋งค ๋ Œ๋”๋ง๋งˆ๋‹ค ์ƒˆ ํ•จ์ˆ˜ ์ƒ์„ฑ
  // โ†’ Button์ด props ๋ณ€๊ฒฝ์œผ๋กœ ์ธ์‹ํ•ด์„œ ๋งค๋ฒˆ ๋ฆฌ๋ Œ๋”๋ง
  const badIncrement = () => setCount(c => c + 1)

  // useCallback ์‚ฌ์šฉ โ€” ํ•จ์ˆ˜ ์ฐธ์กฐ ์œ ์ง€
  // โ†’ ์˜์กด์„ฑ์ด ๋ฐ”๋€Œ์ง€ ์•Š์œผ๋ฉด Button์ด ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Œ
  const increment = useCallback(() => {
    setCount(c => c + 1)
  }, [])  // ์˜์กด์„ฑ ์—†์Œ โ€” ํ•ญ์ƒ ๊ฐ™์€ ํ•จ์ˆ˜

  const decrement = useCallback(() => {
    setCount(c => c - 1)
  }, [])

  return (
    <div>
      <p>์นด์šดํŠธ: {count}</p>
      <input value={text} onChange={e => setText(e.target.value)} />
      <Button onClick={increment} label="์ฆ๊ฐ€" />
      <Button onClick={decrement} label="๊ฐ์†Œ" />
    </div>
  )
}

React.memo + useCallback ์กฐํ•ฉ์˜ ์‹ค์ œ ํšจ๊ณผ

// ๋ฆฌ์ŠคํŠธ ํ•ญ๋ชฉ ์ปดํฌ๋„ŒํŠธ โ€” memo๋กœ ์ตœ์ ํ™”
const TodoItem = memo(({ todo, onToggle, onDelete }) => {
  console.log(`TodoItem ${todo.id} ๋ Œ๋”๋ง`)
  return (
    <li>
      <input type="checkbox" checked={todo.done} onChange={() => onToggle(todo.id)} />
      {todo.text}
      <button onClick={() => onDelete(todo.id)}>์‚ญ์ œ</button>
    </li>
  )
})

function TodoList() {
  const [todos, setTodos] = useState([...])
  const [filter, setFilter] = useState('all')

  // useCallback์œผ๋กœ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ โ€” todos๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ํ•จ์ˆ˜ ์ฐธ์กฐ ์œ ์ง€
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t))
  }, [])  // setTodos๋Š” ์•ˆ์ •์ ์ด๋ฏ€๋กœ ์˜์กด์„ฑ ๋ถˆํ•„์š”

  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(t => t.id !== id))
  }, [])

  // filter๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ handleToggle, handleDelete๋Š” ์žฌ์ƒ์„ฑ๋˜์ง€ ์•Š์Œ
  // โ†’ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์€ TodoItem์€ ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Œ
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={handleToggle}
          onDelete={handleDelete}
        />
      ))}
    </ul>
  )
}

7. useContext: ์ „์—ญ ์ƒํƒœ ๊ณต์œ 

useContext๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Props Drilling ์—†์ด ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์˜ ์–ด๋А ๊ณณ์—์„œ๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค.

import { createContext, useContext, useState } from 'react'

// 1. Context ์ƒ์„ฑ
const ThemeContext = createContext(null)

// 2. Provider ์ปดํฌ๋„ŒํŠธ
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light')

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light')
  }

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

// 3. Context๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปค์Šคํ…€ ํ›… (ํŽธ์˜์„ฑ + ์—๋Ÿฌ ์ฒ˜๋ฆฌ)
function useTheme() {
  const context = useContext(ThemeContext)
  if (!context) {
    throw new Error('useTheme์€ ThemeProvider ์•ˆ์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค')
  }
  return context
}

// 4. ๊นŠ์ด ์ค‘์ฒฉ๋œ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ
function DeepComponent() {
  const { theme, toggleTheme } = useTheme()

  return (
    <div
      style={{
        backgroundColor: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#333' : '#fff',
        padding: '20px',
      }}
    >
      <p>ํ˜„์žฌ ํ…Œ๋งˆ: {theme}</p>
      <button onClick={toggleTheme}>ํ…Œ๋งˆ ์ „ํ™˜</button>
    </div>
  )
}

// 5. ์ตœ์ƒ์œ„์—์„œ Provider๋กœ ๊ฐ์‹ธ๊ธฐ
function App() {
  return (
    <ThemeProvider>
      <Page />
    </ThemeProvider>
  )
}

์—ฌ๋Ÿฌ Context ์กฐํ•ฉํ•˜๊ธฐ

// ์ธ์ฆ Context
const AuthContext = createContext(null)

function AuthProvider({ children }) {
  const [user, setUser] = useState(null)

  const login = async (email, password) => {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    })
    const userData = await response.json()
    setUser(userData)
  }

  const logout = () => {
    setUser(null)
    localStorage.removeItem('token')
  }

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  )
}

function useAuth() {
  return useContext(AuthContext)
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
function Header() {
  const { user, logout } = useAuth()
  const { theme } = useTheme()

  return (
    <header style={{ backgroundColor: theme === 'dark' ? '#222' : '#f5f5f5' }}>
      {user ? (
        <>
          <span>์•ˆ๋…•ํ•˜์„ธ์š”, {user.name}๋‹˜</span>
          <button onClick={logout}>๋กœ๊ทธ์•„์›ƒ</button>
        </>
      ) : (
        <a href="/login">๋กœ๊ทธ์ธ</a>
      )}
    </header>
  )
}

8. ์ปค์Šคํ…€ ํ›… ๋งŒ๋“ค๊ธฐ

์ปค์Šคํ…€ ํ›…์€ ์—ฌ๋Ÿฌ Hook์„ ์กฐํ•ฉํ•ด์„œ ๋กœ์ง์„ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋‹จ์œ„๋กœ ์ถ”์ถœํ•œ ๊ฒƒ์ด๋‹ค. ์ด๋ฆ„์€ ๋ฐ˜๋“œ์‹œ use๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค.

useFetch: ๋ฐ์ดํ„ฐ ํŽ˜์นญ ํ›…

import { useState, useEffect, useCallback } from 'react'

function useFetch(url) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  const fetchData = useCallback(async () => {
    if (!url) return

    setLoading(true)
    setError(null)

    const controller = new AbortController()

    try {
      const response = await fetch(url, { signal: controller.signal })

      if (!response.ok) {
        throw new Error(`HTTP Error: ${response.status}`)
      }

      const json = await response.json()
      setData(json)
    } catch (err) {
      if (err.name !== 'AbortError') {
        setError(err.message)
      }
    } finally {
      setLoading(false)
    }

    return () => controller.abort()
  }, [url])

  useEffect(() => {
    fetchData()
  }, [fetchData])

  return { data, loading, error, refetch: fetchData }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
function UserList() {
  const { data: users, loading, error, refetch } = useFetch('/api/users')

  if (loading) return <div>๋กœ๋”ฉ ์ค‘...</div>
  if (error) return <div>์—๋Ÿฌ: {error} <button onClick={refetch}>์žฌ์‹œ๋„</button></div>
  if (!users) return null

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

useLocalStorage: localStorage ๋™๊ธฐํ™” ํ›…

import { useState, useEffect } from 'react'

function useLocalStorage(key, initialValue) {
  // ์ง€์—ฐ ์ดˆ๊ธฐํ™”๋กœ localStorage์—์„œ ์ฝ๊ธฐ
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = localStorage.getItem(key)
      return item ? JSON.parse(item) : initialValue
    } catch (error) {
      console.error(`useLocalStorage ์ดˆ๊ธฐํ™” ์—๋Ÿฌ (key: ${key}):`, error)
      return initialValue
    }
  })

  const setValue = (value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value
      setStoredValue(valueToStore)
      localStorage.setItem(key, JSON.stringify(valueToStore))
    } catch (error) {
      console.error(`useLocalStorage ์ €์žฅ ์—๋Ÿฌ (key: ${key}):`, error)
    }
  }

  const removeValue = () => {
    try {
      setStoredValue(initialValue)
      localStorage.removeItem(key)
    } catch (error) {
      console.error(error)
    }
  }

  return [storedValue, setValue, removeValue]
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
function Settings() {
  const [theme, setTheme, removeTheme] = useLocalStorage('theme', 'light')
  const [language, setLanguage] = useLocalStorage('language', 'ko')

  return (
    <div>
      <label>
        ํ…Œ๋งˆ:
        <select value={theme} onChange={e => setTheme(e.target.value)}>
          <option value="light">๋ผ์ดํŠธ</option>
          <option value="dark">๋‹คํฌ</option>
        </select>
      </label>
      <label>
        ์–ธ์–ด:
        <select value={language} onChange={e => setLanguage(e.target.value)}>
          <option value="ko">ํ•œ๊ตญ์–ด</option>
          <option value="en">English</option>
        </select>
      </label>
      <button onClick={removeTheme}>ํ…Œ๋งˆ ์ดˆ๊ธฐํ™”</button>
    </div>
  )
}

useDebounce: ์ž…๋ ฅ ์ง€์—ฐ ์ฒ˜๋ฆฌ ํ›…

import { useState, useEffect } from 'react'

function useDebounce(value, delay = 300) {
  const [debouncedValue, setDebouncedValue] = useState(value)

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value)
    }, delay)

    return () => clearTimeout(timer)  // value๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์ด์ „ ํƒ€์ด๋จธ ์ทจ์†Œ
  }, [value, delay])

  return debouncedValue
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ: ๊ฒ€์ƒ‰์ฐฝ โ€” ์ž…๋ ฅ์„ ๋ฉˆ์ถ”๊ณ  300ms ํ›„์— ๊ฒ€์ƒ‰ ์‹คํ–‰
function SearchBox() {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebounce(query, 300)
  const { data: results, loading } = useFetch(
    debouncedQuery ? `/api/search?q=${debouncedQuery}` : null
  )

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ..."
      />
      {loading && <p>๊ฒ€์ƒ‰ ์ค‘...</p>}
      {results && results.map(r => <p key={r.id}>{r.title}</p>)}
    </div>
  )
}

9. Hooks ๊ทœ์น™

Hooks๋Š” ๋‘ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ๊ทœ์น™์„ ๋”ฐ๋ผ์•ผ ํ•œ๋‹ค. ์ด ๊ทœ์น™์„ ์–ด๊ธฐ๋ฉด ์˜ˆ์ธกํ•  ์ˆ˜ ์—†๋Š” ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๊ทœ์น™ 1: ์ตœ์ƒ์œ„์—์„œ๋งŒ ํ˜ธ์ถœ

Hook์€ ๋ฐ˜๋ณต๋ฌธ, ์กฐ๊ฑด๋ฌธ, ์ค‘์ฒฉ ํ•จ์ˆ˜ ์•ˆ์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†๋‹ค.

// ์ž˜๋ชป๋œ ์ฝ”๋“œ
function BadComponent({ isLoggedIn }) {
  if (isLoggedIn) {
    const [user, setUser] = useState(null)  // ์กฐ๊ฑด๋ฌธ ์•ˆ์—์„œ Hook ํ˜ธ์ถœ โ€” ๊ธˆ์ง€!
  }

  for (let i = 0; i < 3; i++) {
    useEffect(() => {})  // ๋ฐ˜๋ณต๋ฌธ ์•ˆ์—์„œ Hook ํ˜ธ์ถœ โ€” ๊ธˆ์ง€!
  }
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ
function GoodComponent({ isLoggedIn }) {
  const [user, setUser] = useState(null)  // ํ•ญ์ƒ ์ตœ์ƒ์œ„์—์„œ

  useEffect(() => {
    if (isLoggedIn) {  // ์กฐ๊ฑด๋ฌธ์€ Hook ์•ˆ์—
      fetchUser()
    }
  }, [isLoggedIn])
}

React๋Š” Hook์ด ํ˜ธ์ถœ๋˜๋Š” ์ˆœ์„œ๋ฅผ ํ†ตํ•ด ๊ฐ Hook์˜ State๋ฅผ ์ถ”์ ํ•œ๋‹ค. ์กฐ๊ฑด์— ๋”ฐ๋ผ Hook์ด ํ˜ธ์ถœ๋˜๋ฉด ์ˆœ์„œ๊ฐ€ ๋‹ฌ๋ผ์ ธ์„œ State๊ฐ€ ๋’ค์„ž์ธ๋‹ค.

๊ทœ์น™ 2: React ํ•จ์ˆ˜์—์„œ๋งŒ ํ˜ธ์ถœ

Hook์€ React ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ๋‚˜ ์ปค์Šคํ…€ ํ›… ์•ˆ์—์„œ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.

// ์ž˜๋ชป๋œ ์ฝ”๋“œ
function regularFunction() {
  const [state, setState] = useState(0)  // ์ผ๋ฐ˜ ํ•จ์ˆ˜์—์„œ Hook ํ˜ธ์ถœ โ€” ๊ธˆ์ง€!
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ
function MyComponent() {
  const [state, setState] = useState(0)  // ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ โ€” OK
}

function useCustomHook() {
  const [state, setState] = useState(0)  // ์ปค์Šคํ…€ ํ›… โ€” OK
}

eslint-plugin-react-hooks ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ค์น˜ํ•˜๋ฉด ๊ทœ์น™ ์œ„๋ฐ˜ ์‹œ ESLint๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ํ‘œ์‹œํ•ด์ค€๋‹ค.


10. ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜ ๋ชจ์Œ

์‹ค์ˆ˜ 1: useEffect ์˜์กด์„ฑ ๋ˆ„๋ฝ

// ์ž˜๋ชป๋œ ์ฝ”๋“œ: userId๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ effect๊ฐ€ ์žฌ์‹คํ–‰๋˜์ง€ ์•Š์Œ
function UserProfile({ userId }) {
  const [user, setUser] = useState(null)

  useEffect(() => {
    fetchUser(userId).then(setUser)
  }, [])  // userId๋ฅผ ์˜์กด์„ฑ์— ๋„ฃ์ง€ ์•Š์Œ!

  return <div>{user?.name}</div>
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ
useEffect(() => {
  fetchUser(userId).then(setUser)
}, [userId])  // userId ํฌํ•จ

์‹ค์ˆ˜ 2: ๋ถˆํ•„์š”ํ•œ useEffect

// ์ž˜๋ชป๋œ ์ฝ”๋“œ: ํŒŒ์ƒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตณ์ด State + useEffect๋กœ ๊ด€๋ฆฌ
function Order({ items }) {
  const [total, setTotal] = useState(0)

  useEffect(() => {
    setTotal(items.reduce((sum, item) => sum + item.price, 0))
  }, [items])

  return <p>ํ•ฉ๊ณ„: {total}์›</p>
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ: ๋ Œ๋”๋ง ์ค‘์— ์ง์ ‘ ๊ณ„์‚ฐ
function Order({ items }) {
  const total = items.reduce((sum, item) => sum + item.price, 0)
  return <p>ํ•ฉ๊ณ„: {total}์›</p>
}

์‹ค์ˆ˜ 3: ๊ฐ์ฒด/๋ฐฐ์—ด์„ ์˜์กด์„ฑ์— ๋„ฃ์„ ๋•Œ

// ๋ฌธ์ œ: ๋งค ๋ Œ๋”๋ง๋งˆ๋‹ค ์ƒˆ ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜์–ด ๋ฌดํ•œ ๋ฃจํ”„
function Component({ userId }) {
  const options = { method: 'GET' }  // ๋งค๋ฒˆ ์ƒˆ ๊ฐ์ฒด

  useEffect(() => {
    fetch(`/api/users/${userId}`, options)
  }, [options])  // ํ•ญ์ƒ ์ƒˆ ์ฐธ์กฐ์ด๋ฏ€๋กœ ๋งค๋ฒˆ ์‹คํ–‰

  // ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 1: useEffect ์•ˆ์œผ๋กœ ์ด๋™
  useEffect(() => {
    const options = { method: 'GET' }
    fetch(`/api/users/${userId}`, options)
  }, [userId])

  // ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 2: useMemo๋กœ ๋ฉ”๋ชจ์ด์ œ์ด์…˜
  const options = useMemo(() => ({ method: 'GET' }), [])
}

์‹ค์ˆ˜ 4: ๋ Œ๋”๋ง ์ค‘ ๋ถ€์ˆ˜ ํšจ๊ณผ

// ์ž˜๋ชป๋œ ์ฝ”๋“œ
function BadComponent() {
  const [data, setData] = useState(null)
  // ๋ Œ๋”๋ง ์ค‘์— ์ง์ ‘ fetch ํ˜ธ์ถœ โ€” ๋งค ๋ Œ๋”๋ง๋งˆ๋‹ค ์‹คํ–‰๋จ!
  fetch('/api/data').then(r => r.json()).then(setData)
  return <div>{data?.name}</div>
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ
function GoodComponent() {
  const [data, setData] = useState(null)
  useEffect(() => {
    fetch('/api/data').then(r => r.json()).then(setData)
  }, [])
  return <div>{data?.name}</div>
}

11. Hook ํ•œ๋ˆˆ์— ๋น„๊ต

Hook ์šฉ๋„ ๋ฆฌ๋ Œ๋”๋ง ํŠธ๋ฆฌ๊ฑฐ ์ฃผ์š” ์‚ฌ์šฉ ์‚ฌ๋ก€
useState ์ปดํฌ๋„ŒํŠธ ์ƒํƒœ ๊ด€๋ฆฌ ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ํผ, ์นด์šดํ„ฐ, ํ† ๊ธ€
useEffect ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ ์ฒ˜๋ฆฌ ์—†์Œ API ํ˜ธ์ถœ, ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
useRef DOM ์ฐธ์กฐ, ๊ฐ’ ์œ ์ง€ ์—†์Œ ํฌ์ปค์Šค, ํƒ€์ด๋จธ ID ์ €์žฅ
useMemo ๊ณ„์‚ฐ ๊ฒฐ๊ณผ ์บ์‹ฑ ์—†์Œ ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ, ํŒŒ์ƒ ๋ฐ์ดํ„ฐ
useCallback ํ•จ์ˆ˜ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ์—†์Œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— ์ฝœ๋ฐฑ ์ „๋‹ฌ
useContext Context ๊ฐ’ ์ฝ๊ธฐ Context ๋ณ€๊ฒฝ ์‹œ ์ „์—ญ ์ƒํƒœ, ํ…Œ๋งˆ, ์ธ์ฆ

๋งˆ์น˜๋ฉฐ

React Hooks๋Š” ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ•๋ ฅํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” ํ•ต์‹ฌ ๊ธฐ๋Šฅ์ด๋‹ค. ํ•ต์‹ฌ ์›์น™์„ ์ •๋ฆฌํ•˜์ž๋ฉด:

  1. useState: ๊ฐ’์ด ๋ณ€ํ•  ๋•Œ๋งˆ๋‹ค ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ ค์•ผ ํ•œ๋‹ค๋ฉด State๋กœ ๊ด€๋ฆฌ
  2. useEffect: ๋ Œ๋”๋ง ์ดํ›„์— ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ์ž‘์—…(API ํ˜ธ์ถœ, ๊ตฌ๋… ๋“ฑ)์— ์‚ฌ์šฉ
  3. useRef: ๋ Œ๋”๋ง๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ๊ฐ’์„ ์œ ์ง€ํ•˜๊ฑฐ๋‚˜ DOM์— ์ง์ ‘ ์ ‘๊ทผํ•  ๋•Œ
  4. useMemo/useCallback: ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ์‹ค์ œ๋กœ ํ™•์ธ๋˜์—ˆ์„ ๋•Œ๋งŒ ์‚ฌ์šฉ
  5. useContext: Props Drilling์ด ๊นŠ์–ด์งˆ ๋•Œ ์ „์—ญ ์ƒํƒœ ๊ณต์œ 

์ปค์Šคํ…€ ํ›…์„ ์ž˜ ๋งŒ๋“ค๋ฉด ๋กœ์ง์„ ๊น”๋”ํ•˜๊ฒŒ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ค‘๋ณต๋˜๋Š” ๋กœ์ง์ด ๋ณด์ธ๋‹ค๋ฉด ์ปค์Šคํ…€ ํ›…์œผ๋กœ ์ถ”์ถœํ•˜๋Š” ์Šต๊ด€์„ ๋“ค์ด์ž.



๊ด€๋ จ ๊ธ€

@leekh8
๋ณด์•ˆ, ์›น ๊ฐœ๋ฐœ, Python์„ ๋‹ค๋ฃจ๋Š” ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ