모던 리액트 Deep Dive - 리액트 훅

2025. 10. 22. 22:34·Study/모던 리액트 Deep Dive

⚡리액트 핵심요소 깊게 살펴보기 - 3.1 리액트의 모든 훅 파헤치기

🌚 훅(hook) 

함수형 컴포넌트가 상태를 사용하거나 클래스형 컴포넌트의 생명주기 메서드를 대체하는 등의 다양한 작업을 하기 위해 추가됨
훅을 사용하면 클래스형 컴포넌트가 아니더라도 리액트의 다양한 기능을 활용할 수 있음 

3.1 리액트의 모든 훅 파헤치기 

3.1.1 useState

  • 함수형 컴포넌트 내부에서 상태를 정의하고 상태를 관리할 수 있게 해주는 훅

🌚기본 사용법

import {useState} from 'react'
const {state, setState} = useState(initialState)
  • useState 인수로는 state의 초기값을 넘겨주는데, 아무런 값을 넘겨주지 않으면 undefined가 초기값임
  • useState 반환값 : 배열 
  • state:값 자체를 사용할 수 있음
  • setState: 해당 state의 값을 변경함

💡useState를 사용하지 않고, 함수 내부에서 자체적으로 변수를 사용하여 상태값을 관리하면?

  • 함수형 컴포넌트는 매번 함수를 실행해 렌더링이 일어나고, 함수 내부의 값은 함수가 실행될때마다 다시 초기화됨

💡 useState 훅의 결과값은 어떻게 함수가 실행되어 그값을 유지할까?

  • 리액트는 상태값을 메모리에 저장하고, setState는 그 저장공간을 가리키는 클로저를 반환함
    그래서 컴포넌트 함수가 다시 실행돼도 값이 “유지되는 것처럼” 보임

🌕 게으른 초기화 (Lazy initialization)

  • useState에 변수 대신 함수를 넘기는 것
//일반적인 useState 사용
//바로 값을 집어 넣음
const [count, setCount] = useState(
	Number.parseInt(window.localStorage.getItem(cacheKey)),
)

//게으른 초기화 
//함수를 실행해 값을 반환함
const [count, setCount] = useState(() =>
	Number.parseInt(window.localStorage.getItem(cacheKey)),
)
  • 게으른 초기화는 state가 처음 만들어질때만 사용함
  • 함수형 컴포넌트는 렌더링될 때마다 함수 전체가 다시 실행됨
  • 하지만 useState는 기존 state 값을 유지하고, 초기값은 첫 렌더링 때만 사용됨

💡게으른 초기화 언제 함수형 초기화를 쓰면 좋은가

  : useState(값) 대신 useState(() => 값) 형태로 작성하는 게 성능적으로 이득 

  • 무거운 연산이 초기값에 포함될 때
  • localStorage, sessionStorage 접근
  • 배열 메서드 (map, filter, find)로 복잡한 계산

3.1.2 useEffect

  • 애플리케이션 내 컴포넌트의 여러 값을 활용해 동기적으로 부수 효과를 만드는 메커니즘
  • 부수 효과가 언제 일어나는지보다 언제 상태값과 함께 실행되는지 살펴보는 것이 중요함

🌚기본 사용법

function Component() {
	useEffect(() => {
    	//
    },[props,state])
}
첫번째 인수 : 실행할 부수 효과가 포함된 함수
두번째 인수 : 의존성 배열(빈배열or배열생략 가능)

💡useEffect는 어떻게 의존성 배열이 변경된 것을 알고 실행될까?

  • 함수형 컴포넌트는 매번 함수를 실행해 렌더링을 수행한다는 것
  • 렌더링할 때마다 의존성에 있는 값을 보면서 의존성의 값이 이전과 다른 게 하나라도 있으면 부수 효과를 실행하는 것

🌕 클린업 함수의 목적 

import { useState, useEffect } from 'react'

export default function App() {
  const [counter, setCounter] = useState(0)

  function handleClick() {
    setCounter((prev) => prev + 1)
  }

  useEffect(() => {
    function addMouseEvent() {
      console.log(counter)
    }

    window.addEventListener('click', addMouseEvent)

    // 클린업 함수
    return () => {
      console.log('클린업 함수 실행!', counter)
      window.removeEventListener('click', addMouseEvent)
    }
  }, [counter])

  return (
    <>
      <h1>{counter}</h1>
      <button onClick={handleClick}>+</button>
    </>
  )
}


//실행결과
클린업 함수 실행! 0
1

클린업 함수 실행! 1
2

클린업 함수 실행! 2
3

클린업 함수 실행! 2
4
  • 클린업 함수는 새로운 값을 기반으로 렌더링 뒤에 실행되지만, 변경된 값을 읽는 것은 아니라 함수가 정의됐을 당시에 선언됐던 이전 값을 보고 실행된다는 것
  • 함수형 컴포넌트의 useEffect는 콜백이 실행될 때마다 이전의 클린업 함수가 존재한다면, 클린업 함수를 실행한 뒤에 콜백을 실행함
  • 따라서 이벤트를 추가하기 전에 이전에 등록했던 이벤트 핸들러를 삭제하는 코드를 클린업 함수에 추가하는 것
  • 이렇게 함으로써 특정 이벤트의 핸들러가 무한히 추가되는 것을 방지할 수 있음
언마운트: 특정 컴포넌트가 DOM에서 사라진다는 것을 의미하는 클래스형 컴포넌트의 용어
더보기

클린업 함수는 언마운트라기보다는 함수형 컴포넌트가 리렌더링됐을 때 의존성 변화가 있었을 당시 이전의 값을 기준으로 실행되는, 말 그대로 이전 상태를 청소해 주는 개념임

🌕 의존성 배열

  • 빈 배열을 두거나 값을 넘기지 않을때, 리액트가 useEffect는 비교할 의존성이 없다고 판단해 최초 렌더링 직후 실행된 다음부터는 더 이상 실행되지 않음
  • 아무런 값도 넘겨주지 않는다면 이때는 의존성을 비교할 필요 없이 렌더링할 때마다 실행이 필요하다고 판단해 렌더링이 발생할 때마다 실행됨
빈 배열: 최초 렌더링때 실행된 이후 실행되지 않음
아무런 값 없을때 : 렌더링 할 때 마다 실행
원하는 값 : 값이 바뀔 때 마다 실행

💡의존성 배열에 아무런 값이 없는 useEffect가 매 렌더링마다 실행된다면useEffect 없이 써도 되는 게 아닐까? 

// 1
function Component() {
	console.log('렌더링됨');
}

// 2
function Component() {
	useEffect(() => {
    	console.log('렌더링됨;);
    });
}

💡위의 두 코드 차이점

  • 서버 사이드 렌더링 관점에서 useEffect는 클라이언트 사이드에서 실행되는 것을 보장해 준다. useEffect 내부에서는 window 객체의 접근에 의존하는 코드를 사용해도 된다.
  • useEffect는 컴포넌트 렌더링의 부수 효과, 즉 컴포넌트의 렌더링이 완료된 이후에 실행된다. 반면 직접 실행은 컴포넌트가 렌더링되는 도중에 실행된다. 따라서 위와는 달리 서버 사이드 렌더링의 경우에 서버에서도 실행된다. 그리고 이 작업은 함수형 컴포넌트의 반환을 지연시키는 행위다. 즉, 무거운 작업일 경우 렌더링을 방해하므로 성능에 악영향을 미칠 수 있다.
useEffect의 effect는 컴포넌트의 사이드 이펙트, 즉 부수 효과를 의미한다는 것을 명심하고, useEffect는 컴포넌트가 렌더링된 후에 어떠한 부수 효과를 일으키고 싶을 때 사용하는 훅임

🌚useEffect의 구현

  • 의존성 배열의 이전 값고 현재 값의 얕은 비교임
  • Object.is를 기반으로 하는 얕은 비교를 수행함
  • 이전 의존성 배열과 현재 의존성 배열의 값에 하나라도 변경 사항이 있다면 callback으로 선언한 부수 효과를 실행하며, 이것이 useEffect의 본질임

🌚useEffect 사용시 주의해야 할 점

  • eslint-disable-line react-hooks/exhaustive-deps 주석은 최대한 자제하라
    • useEffect는 의존성 배열에 명시된 값의 변화에 의해 실행되는 훅이고, 의존성 배열을 생략하고 내부에서 값을 사용하면, 그 값의 변경과 effect 실행이 연결되지 않아 의도치 않은 동작이 발생함
  • useEffect의 첫 번째 인수에 함수명을 부여하라
    • useEffect가 단순할 땐 익명 함수도 괜찮지만, 복잡해질수록 함수명을 부여해 목적을 명확히 하고 책임을 좁히는 것이 좋음
  • 거대한 useEffect를 만들지 마라
    • useEffect는 렌더링 이후 실행되지만,복잡하거나 무거운 로직은 여전히 성능에 영향을 주기때문에, 큰 effect 하나보다 의존성이 적은 작은 effect 여러 개로 분리하는 것이 좋음
  • 불필요한 외부 함수를 만들지 마라
    • useEffect 내부에서 비동기 코드를 직접 사용하면 요청 순서가 뒤섞이는 경쟁 상태(race condition) 가 발생할 수 있음
    • React는 이를 방지하고 cleanup 순서를 보장하기 위해 useEffect(async () => …) 형태를 지원하지 않음

3.1.3 useMemo

  • 비용이 큰 연산에 대한 결과를 저장해두고, 저장된 값을 반환하는 훅
  • 리액트에서 최적화를 떠올릴때 사용
첫 번째 인수 : 어떠한 값을 반환하는 생성 함수
두 번째 인수 : 해당 함수가 의존하는 값의 배열을 전달함
  • useMemo는 렌더링 시 의존성 배열(deps) 안의 값이 변경되지 않았다면 이전에 계산한 값을 그대로 반환하고, 의존성 배열의 값이 변경되면 첫 번째 인수로 전달한 함수가 다시 실행되고, 그 결과값을 반환하며 기억함
import { useMemo } from 'react'

const memoizedValue = useMemo(() => expensiveComputation(a,b), [a,b])

3.1.4 useCallback

useMemo가 값을 기억했다면, useCallback은 인수로 넘겨받은 콜백 자체를 기억함
즉 useCallback은 특정 함수를 새로 만들지 않고 다시 재사용한다는 의미

🌚useCallback ( useMemo와 동일하게 의존성 배열이 변경되지 않는 한 함수를 재생성하지 않음)

첫 번째 인수 : 함수
두 번째 인수 : 의존성 배열
  • 함수의 재생성을 막아 불필요한 리소스 또는 리렌더링을 방지하고 싶을 때 사용

🌕useMemo와 useCallback의 차이

  • 메모이제이션을 하는 대상이 변수냐 함수냐인 것
  • useMemo로 useCallback을 구현하는 경우 불필요하게 코드가 길어지고 혼동을 야기할 수 있으므로 리액트에서 별도로 제공하는 것으로 추측해 볼 수 있음
const handleClick1 = useCallback(() => {
	setCounter((prev) => prev + 1);
}, []);

const handleClick2 = useMemo(() => {
	return () => setCounter((prev) => prev + 1);
}, []);

💡useCallback 사용 예시

import React, { useState, useCallback } from "react";

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

  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []); // 의존성이 없으므로 처음 한 번만 생성됨

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>+</button>
    </div>
  );
}

export default Counter;
  • handleClick 함수는 매 렌더링마다 새로 만들어지지 않음
    따라서 이 함수를 props로 넘길 때, 자식 컴포넌트의 불필요한 리렌더링을 막을 수 있음

3.1.5 useRef

  • useState와 동일하게 컴포넌트 내부에서 렌더링이 일어나도 변경 가능한 상태값을 저장함

🌚useState와 구별되는 큰 차이점

  1. useRef는 반환값인 객체 내부에 있는 current로 값에 접근 또는 변경할 수 있다.
  2. useRef는 그 값이 변하더라도 렌더링을 발생시키지 않는다.

useRef로 useState를 흉내내도 렌더링이 되지 않음
🌕 useRef가 왜 필요할까?

  • 컴포넌트가 실행되어 렌더링 되지 않았음에도 value라는 값이 기본적으로 존재하게 되는데, 이는 메모리에 불필요한 값을 갖게 하는 악영향을 미침
  • 컴포넌트가 여러 번 생성된다면 각 컴포넌트에서 가리키는 값이 모두 value로 동일함
  • useRef는 컴포넌트가 렌더링될 때만 생성되며, 컴포넌트 인스턴스가 여러 개라도 각각 별개의 값을 바라봄

🌕 useRef의 가장 일반적인 사용 > DOM에 접근하고 싶을 때

function RefComponent() {
  const inputRef = useRef()
  
  // 이땐 렌더링이 실행되기 전으로 undefined를 반환
  console.log(inputRef.current) // undefined
  
  useEffect(() => {
    console.log(inputRef.current) // <input type="text"></input>
  }, [input])
  
  return <input ref={inputRef} type="text" />
}
  • useRef는 최초에 넘겨받은 기본값을 가지고 있음
  • 여기서 중요한 점은 useRef의 최초 기본값은 return 문에 정의해 둔 DOM이 아닌 useRef()로 넘겨받은 인수인 것!

🌕 useRef를 사용할수 있는 유용한 경우

렌더링을 발생시키지 않고 원하는 상태값을 저장할 수 있다는 특징을 활용하여 useState의 이전 값을 저장하는 usePrevious() 같은 훅을 구현할 때 유용함

💡 useRef를 활용한 usePrevious() 훅 구현

function usePrevious(value) {
  const ref = useRef()
  useEffect(() => {
    ref.current = value
  }, [value]) // value가 변경되면 그 값을 ref에 넣음
  return ref.current
}

function SomeComponent() {
  const [counter, setCounter] = useState(0)
  const previousCounter = usePrevious(counter)
  
  function handleClick() {
    setCounter((prev) => prev + 1)
  }
  
  // 0 (undefined)
  // 1, 0
  // 2, 1
  // 3, 2
  
  return (
    <button onClick={handleClick}>
      {counter} {previousCounter}
    </button>
  )
}

개발자가 원하는 시점의 값을 렌더링에 영향을 미치지 않고 보관해주도 싶다면 useRef를 사용하는 것을 추천함!


3.1.6 useContext

🌚Context란

//props 내려주기
<A props={somthing}>
  <B props={somthing}>
    <C props={somthing}>
      <D props={somthing}/>
    </C>
  </B>
</A>
  • props내려주기를 극복하기 위해 등장한 개념으로, 명시적인 props 전달 없이도 선언한 하위 컴포넌트 모두에서 자유롭게 원하는 값을 사용할 수 있음

💡Context를 함수형 컴포넌트에서 사용할 수 있게 해주는 useContext 훅

const Context = createContext<{ hello: string }|undefined>()

function ParentComponent() {
  return (
    <>
      <Context.Provider value={{hello: 'react'}}>
        <Context.Provider value={{hello: 'javascript'}}>
          <ChildComponent />
        </Context.Provider>
      </Context.Provider>
    </>
  )
}

function ChildComponent() {
  const value = useContext(Context);
  //react가 아닌 javascript가 반환된다.
  return <>{value ? value.hello : ''}</>
}
  • 상위 컴포넌트에서 만들어진 Context를 함수형 컴포넌트에서 사용할 수 있도록 만들어진 훅
  • 상위 컴포넌트 어딘가에서 선언된 <Context.Provider/>에서 제공한 값을 사용할 수 있게 됨
  • 컴포넌트 트리가 복잡해질수록 Context를 사용하는 것이 쉽지 않음 (useContext 내부에서 해당 콘텍스트가 존재하는 환경인지, 즉 콘텍스트가 한번이라도 초기화되어 값을 내려주고 있는지 확인해보면 됨)

💡 useContext 사용시 주의할 점

useContext를 함수 컴포넌트 내부에서 사용할 때는 항상 컴포넌트 재활용이 어려워진다는 점을 인지해야 되며, useContext가 선언돼 있으면 Provider에 의존성을 가지고 있는 셈이 되므로 아무데서나 재활용하기에는 어려운 컴포넌트가 됨
해당 함수 컴포넌트가 Provider 하위에 있지 않은 상태로 useContext를 사용한다면 예기치 못한 작동 방식이 만들어지므로 즉, useContext가 있는 컴포넌트는 그 순간부터 눈으로는 직접 보이지도 않을 수 있는 Provider와의 의존성을 갖게 됨
  • 이러한 상황을 방지하려면 useContext를 사용하는 컴포넌트를 최대한 작게 하거나 혹은 재사용되지 않을 만한 컴포넌트에서 사용되어야 함
  • Context는 상태를 주입해 주는 API이고, 상태 관리 라이브러리가 되기 위해서는 최소한 다음 두 가지 조건을 만족해야 함
  • 어떠한 상태를 기반으로 다른 상태를 만들어 낼 수 있어야 함
  • 필요에 따라 이러한 상태 변화를 최적화할 수 있어야 함
  • 그러나 Context는 둘 중 어느것도 하지 못하고, 단순히 Props의 값을 하위로 전달해 줄 뿐, useContext를 사용한다고 해서 렌더링이 최적화되지는 않음!

3.1.7 useReducer

  • useState의 심화버전
  • 상태(state)가 복잡하거나 여러 상태가 하나의 로직으로 묶여야 할 때 사용
  • useState처럼 [state, setState]를 반환하지만, 대신 [state, dispatch]를 반환함
state: 현재 상태 값
dispatch: 상태를 바꾸기 위한 함수 (setState 대신 action을 전달)

💡useReducer의 사용법

// useReducer가 사용할 state 정의
type State = {
    count: number   
}

// state 변화를 발생시킬 action의 타입과 넘겨줄 값(payload)를 정의
// type과 paylooad라는 네이밍을 지킬 필요는 없으며, 객체일 필요도 없음
type Action = { type: 'up' | 'down' | 'reset'; payload?: State }

// 무거운 연산이 포함된 게으른 초기화 함수
function init(count:State): State {
    // count: State를 받아서 초기값을 어떻게 정의할지 연산하면 됨
    return count
}

// 초깃값
const initialState: State = { count: 0 }

// 앞서 선언한 state와 action을 기반으로 state가 어떻게 변경될지 정의
function reducer(state: State, action: Action): State {
    switch (action.type) {
        case 'up':
            return { count: state.count + 1 }
        case 'down':
            return { count: state.count - 1 > 0 ? state.count - 1 : 0 }
        case 'reset':
            return init(action.payload || { count: 0 })
        default:
            throw new Error(`UnExpected action type ${action.type}`)
    }
}

export default function App() {
    const [state, dispatch] = React.useReducer(reducer, initialState, init)

    function handleUpButtonClick() {
        dispatch({ type: 'up' })
    }

    function handleDownButtonClick() {
        dispatch({ type: 'down' })
    }

    function handleResetButtonClick() {
        dispatch({ type: 'reset', payload: { count: 0 } })
    }

    return (
        <div className="App">
            <h1>{state.count}</h1>
            <button onClick={handleUpButtonClick}>+</button>
            <button onClick={handleDownButtonClick}>-</button>
            <button onClick={handleResetButtonClick}>reset</button>
        </div>
    )
}

💡useReducer의 목적

  • 복잡한 형태의 state를 체계적으로 관리하기 위해 사용하고, 미리 정의된 dispatcher로만 state를 변경할 수 있게 만들어, 컴포넌트 내부 로직을 단순화하고 상태 변화 과정을 명확히 함
더보기

복잡한 상태(state)를 다룰 때,그 변화를 명확한 규칙(dispatcher와 reducer) 으로 관리하기 위해 사용함
즉, 상태를 바꾸는 방식을 미리 정해둔 함수(reducer) 로 사용하는 것

💡useState와의 비교
useState  : 단순한 값(number, boolean 등)을 관리할 때
useReducer: 여러 개의 상태를 함께 관리하거나, 상태 간의 연관성이 있는 복잡한 로직일 경우 


3.1.8 useImperativeHandle

  • 실제 개발과정에서 자주 볼수 없는 훅 
forwardRef가 통로를 만들어주고, useImperativeHandle이 그 통로를 커스터마이징한다.

🌚 React.forwardRef

  • ref는 useRef에서 반환한 객체로, 리액트 컴포넌트의 props인 ref에 넣어 HTMLElement에 접근하는 용도로 흔히 사용
  • key와 마찬가지로 ref도 리액트에서 컴포넌트의 props로 사용할 수 있는 예약어로 별도 선언되어있지 않더라도 사용할 수 있음
더보기

부모 컴포넌트가 자식 컴포넌트 내부의 DOM이나 메서드에 직접 접근할 수 있도록 ref를 전달해주는 함수

기본적으로 ref는 DOM 요소에만 붙일 수 있는데, forwardRef를 사용하면 함수형 컴포넌트에도 ref를 전달할 수 있음

const MyInput = React.forwardRef((props, ref) => {
  return <input ref={ref} />;
});

// 부모에서
const inputRef = useRef();
<MyInput ref={inputRef} />;

🌚useImperativeHandle이란

useImperativeHandle(ref, createHandle)
  • 부모에게서 넘겨받은 ref를 원하는대로 수정할 수 있는 훅( ref를 통해 노출할 값(또는 메서드)을 커스터마이징하는 Hook )
  • forwardRef로 받은 ref에 직접 제어할 수 있는 함수나 값들을 지정할 수 있음

💡useImperativeHandle의 사용법 

const Input = forwardRef((props, ref) => {
    // useImprerativeHandle를 사용하면 ref의 동작을 추가로 정의할 수 있음
    useImperativeHandle(
    	ref,
        () => ({
        alert: () => alert(props.value),
    }), 
    // useEffect의 deps와 같음
    [props.value],
    )
    return <input ref={ref} {...props}/>;
});

function App() {
    // input에서 사용할 ref
    const inputRef = useRef();

    // input의 value
    const [text, setText] = useState("");

    function handleClick() {
        // inputRef에 추가한 alert이라는 동작을 사용
        inputRef.current.alert();
    }

    function handleChange(e) {
        setText(e.target.value);
    }

    return (
        <>
            <Input ref={inputRef} value={text} onChange={handleChange} />
            <button onClick={handleClick}>Focus</button>
        </>
    )
}
더보기

💡사용하는 이유 

보통 React는 단방향 데이터 흐름을 따르지만, 어떤 경우엔 부모가 자식의 동작을 “명령적으로 제어”해야 할 때가 있음
(예: focus(), scrollToTop(), resetForm() 등)

이때 forwardRef + useImperativeHandle 조합을 쓰면 필요한 기능만 제한적으로 외부에 노출할 수 있음


3.1.9 useLayoutEffect

공식문서에 따르면 아래와 같이 정의되고 있음 

이 함수의 시그니처는 useEffect와 동일하나, 모든 DOM의 변경 후에 동기적으로 발생한다.

모든 DOM의 변경 후에 useLayoutEffect의 콜백 함수 실행이 동기적으로 발생한다는 점

🌚useLayoutEffect 실행 순서
  1. 리액트가 DOM을 업데이트
  2. useLayoutEffect를 실행
  3. 브라우저에 변경 사항을 반영
  4. useEffect를 실행

  • useLayoutEffect가 useEffect보다 먼저 실행되는데, useLayoutEffect가 브라우저에 변경사항이 반영되기 전에 실행되는 반면 useEffect는 브라우저에 변경사항이 반영된 이후에 실행되기 때문임
  • 동기적으로 발생한다는 것은 리액트는 useLayoutEffect의 실행이 종료될때까지 기다린 다음에 화면을 그린다는 것을 의미함
  • 즉, 리액트 컴포넌트는 useLayoutEffect가 완료될때까지 기다리기 때문에 컴포넌트가 잠시동안 일시정지되는 것같은 일이 발생함

💡useLayoutEffect는 언제 사용하는 것이 좋을까?

DOM은 계산되었지만, 화면이 반영되기 전에 하고 싶은 작업이 있을때와 같이 반드시 필요할 때만 사용것이 좋음

 

 


3.1.10 useDebugValue

  • 일반적으로 프로덕션 웹서비스에서 사용하는 훅
  • 리액트 애플리케이션을 개발하는 과정에서 사용되는데, 디버깅하고 싶은 정보를 해당 훅에 사용하면 리액트 개발자 도구에서 볼수 있음
  • 사용자 정의 훅 내부의 내용에 대한 정보를 남길 수 있는 훅
useDebugValue(value, formatFunction)
//첫 번째 인수: 표시할 값
//두 번째 인수(선택): 값이 표시되기 전 가공하는 함수

3.1.11 훅의 규칙 (Rules of Hooks)

  • React에서는 훅을 사용할 때 반드시 지켜야 하는 두 가지 핵심 규칙이 있음
  • 이 규칙은 ESLint 플러그인,eslint-plugin-react-hooks의 rules-of-hooks 규칙에서도 강제할 수 있음

1. 최상위에서만 훅을 호출해야함.

반복문, 조건문, 중첩된 함수 안에서 훅을 호출하면 안 되며, 항상 컴포넌트의 최상위 레벨에서만 호출해야 함

즉, 렌더링할 때마다 훅이 항상 동일한 순서로 호출되어야 함.

2. 훅은 오직 React 함수형 컴포넌트나 커스텀 훅 내부에서만 호출할 수 있음 일반 자바스크립트 함수에서는 훅을 쓸 수 없음

function Component() {
  const [count, setCount] = useState(0);
  const [required, setRequired] = useState(false);

  useEffect(() => {
    // count 또는 required가 바뀔 때 실행
  }, [count, required]);
}

'Study > 모던 리액트 Deep Dive' 카테고리의 다른 글

모던 리액트 Deep Dive - 서버 사이드 랜더링을 위한 리액트 API  (0) 2025.11.10
모던 리액트 Deep Dive - 리액트 훅과 고차 컴포넌트  (0) 2025.11.01
모던 리액트 Deep Dive - 메모이제이션  (0) 2025.10.14
모던 리액트 Deep Dive - 렌더링  (0) 2025.10.14
모던 리액트 Deep Dive - 컴포넌트  (0) 2025.10.07
'Study/모던 리액트 Deep Dive' 카테고리의 다른 글
  • 모던 리액트 Deep Dive - 서버 사이드 랜더링을 위한 리액트 API
  • 모던 리액트 Deep Dive - 리액트 훅과 고차 컴포넌트
  • 모던 리액트 Deep Dive - 메모이제이션
  • 모던 리액트 Deep Dive - 렌더링
happy_dev
happy_dev
복사하고 붙여넣기 잘하고 싶어요
  • happy_dev
    happy의 개발일지
    happy_dev
  • 전체
    오늘
    어제
    • 전체 (43)
      • Java (0)
      • React (1)
      • DB (1)
      • Study (41)
        • 친절한 SQL 튜닝 (9)
        • 모던 리액트 Deep Dive (18)
        • 젠킨스로 배우는 CI,CD 파이프라인 구축 (14)
        • Studyd (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Jenkins
    toad
    인덱스의기본
    oracle
    DB
    젠킨스
    조인
    SQL
    리액트
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
happy_dev
모던 리액트 Deep Dive - 리액트 훅
상단으로

티스토리툴바