FEInterview Prep

Velog

리액트가 마침내 가장 큰 문제를 해결했습니다 (useEffectEvent)

useEffectEvent'이펙트 내부에서 호출되지만 의존성 배열에 들어가지 않아야 할 함수' 를 위한 전용 훅이다. useRef 우회와 useCallback 의 수년간의 패치 레이어가 정리된다.

2026-02-09·6분 읽기
React
원문 보기 ↗

핵심 요약

useEffect 에서 함수를 호출할 때 개발자들은 두 가지 속성을 동시에 원했다. (1) 그 함수 안에서는 최신 props/state 를 보고 싶다, (2) 그 함수가 바뀌어도 이펙트가 재시작되면 안 된다. 이 두 요구는 의존성 배열 시스템과 정면 충돌했고 useRef, useCallback, ESLint exhaustive-deps disable 등 수년간 누더기 해법이 쌓였다. useEffectEvent 는 이 충돌을 해소하는 전용 훅이다. 훅으로 감싼 함수는 (a) 이펙트 내부에서 호출되면 항상 최신 렌더의 클로저 를 본다, (b) 의존성 배열에 포함하면 안 된다(React 가 문법적으로 막는다). 실전에서는 'socket 연결은 roomId 에만 반응하고, 메시지 수신 시엔 최신 theme 을 반영' 같은 패턴을 10줄짜리 코드로 압축한다.

리액트 이펙트 문제를 '반응 이유(reactive dependency)''읽기 전용 값(latest value)' 두 축으로 본다. 의존성 배열에 들어갈 것은 '이 값이 바뀌면 이펙트를 다시 실행해야 하는가' 의 답이 Yes 인 것만. useEffectEvent 는 이 분리를 문법적으로 강제한다.

리액트 면접에서 'stale closure'useEffect 의존성 은 단골. 과거의 답은 항상 어색했다.

  • 'ref 에 담아서 쓴다' — 반응성을 포기
  • 'ESLint 룰 disable 한다' — 의존성 버그의 온상
  • useCallback 으로 wrap — 또 다른 의존성 문제

useEffectEvent의도를 문법으로 표현한다. '이 함수는 이펙트 안에서 호출되지만, 이 함수가 바뀌어서 이펙트가 재실행되어서는 안 된다.'

학습 포인트

면접 답변으로 연결할 학습 포인트입니다.

반응 vs 읽기 — 훅으로 분리

useEffectEvent 의 핵심은 '언제 재실행하느냐''무엇을 읽느냐' 를 분리한 것.

function ChatRoom({ roomId, theme }) {
  const onMessage = useEffectEvent((msg) => {
    // 항상 최신 theme 을 본다
    showNotification(msg, theme);
  });

  useEffect(() => {
    const socket = createSocket(roomId);
    socket.on('message', onMessage); // 의존성에 넣지 않는다
    return () => socket.close();
  }, [roomId]); // roomId 만 진짜 반응 의존
}

과거에는 theme 을 의존성에 넣으면 매번 소켓이 재연결되고, 빼면 stale theme 이었다.

useEffectEventstale closurereactiveEffect
자주 하는 오해

useEffectEvent 로 감싼 함수를 자식 컴포넌트 prop 으로 내려주는 것. React 는 이를 에러 로 처리 — 용도는 오직 이펙트 내부 호출 이다.

useCallback 과의 차이

둘 다 '함수를 안정화' 하지만 목적이 다르다.

언제 바뀌나어디서 쓰나최신 값
useCallbackdeps 바뀔 때자식 prop (메모이제이션)deps 시점 클로저
useEffectEvent매 렌더 새로이펙트 내부 호출항상 최신

useCallback 은 참조 동일성 최적화, useEffectEvent 는 의존성 설계 도구. 헷갈리면 '자식에게 넘기나?' 로 판단 — 넘긴다면 useCallback, 이펙트에서만 쓴다면 useEffectEvent.

useCallbackmemoizationreferential equalitychild prop
자주 하는 오해

useEffectEvent 를 '더 좋은 useCallback' 으로 오해하는 것. 용도가 다르다.

마이그레이션 트리거

기존 코드에서 useEffectEvent 로 바꿔야 할 신호 세 가지:

  1. useEffect 의존성 배열에서 특정 값을 일부러 빼고 // eslint-disable-next-line 주석
  2. useRef 를 선언하고 렌더마다 ref.current = fn 으로 덮어씌우는 패턴
  3. useCallback deps 가 매 렌더 바뀌어 실제로 캐시 효과가 없는 함수

이 세 패턴은 거의 그대로 useEffectEvent 로 정리된다.

migrationeslint-disableref patterncode smell
자주 하는 오해

모든 useCallback 을 바꾸려 드는 것. 자식 prop 용으로 쓰는 useCallback 은 그대로 둬야 한다.

읽는 순서

  1. 1이론

    React 공식 문서 'Separating Events from Effects' 챕터를 읽고, '반응 의존성' vs '읽기 값' 테이블을 본인 코드의 useEffect 5개에 적용해 분류.

  2. 2구현

    채팅 소켓 예제(roomId 로 연결, theme 으로 알림) 를 세 가지 스타일로 구현 후 비교: (a) useRef, (b) deps 에 모두 넣기, (c) useEffectEvent.

  3. 3실무

    본인 프로젝트의 // eslint-disable-next-line react-hooks/exhaustive-deps 를 모두 찾아 useEffectEvent 로 해소할 수 있는지 분류하고 PR 작성.

  4. 4설명

    팀에 'useCallback vs useEffectEvent — 어떤 상황에 무엇을?' 플로차트 한 장을 공유.

면접 연결 질문

medium`useEffectEvent` 로 감싼 함수를 **자식 컴포넌트 prop 으로 전달** 하면 어떻게 되나요?
힌트

[좋은 답변] React 가 런타임 에러 — '이펙트 내부에서만 호출 가능' 제약을 강제. 이는 반응성 제거 함수가 렌더 트리 밖으로 새면 버그 유발이 확정적이기 때문. 자식에 내려주려면 useCallback 을 써야 한다.

hard기존의 `useRef` + 최신 값 저장 패턴과 `useEffectEvent` 의 본질적 차이는?
힌트

[좋은 답변] ref 패턴은 수동이라 업데이트를 잊으면 stale, 그리고 tearing 가능성. useEffectEventReact 가 매 렌더 후 자동 갱신 + 의존성 시스템과 명시적 계약 + ESLint 가 오용 감지 — 세 가지 모두 수동 해법으로는 불가능.

medium`useEffect` 의존성 배열에서 `// eslint-disable` 을 쓴 코드를 리뷰한다면 어떤 기준으로 리팩토링을 요청하시겠어요?
힌트

[좋은 답변] disable 한 의존성이 (1) 반응 필요 — 배열에 넣어라, (2) 값만 읽기 — useEffectEvent 로 감싸라. 중간은 없다. ESLint 주석은 '의도를 문법으로 표현하지 못한 흔적'.

자기 점검

`useEffectEvent` 를 **언제 쓰면 안 되는지** 두 가지 예를 들어보세요.
자식 prop렌더 중 호출이벤트 핸들러 직접 연결JSX 내부
자주 하는 오해

모든 콜백에 만능으로 쓴다. 핵심은 이펙트 내부 전용.

현재 코드베이스에서 `useRef` 로 최신 함수를 저장하는 패턴을 3곳 찾고, 각각 `useEffectEvent` 로 바꿀 수 있는지 판단하세요.
stale closureref 패턴최신 값Effect 내부
자주 하는 오해

ref 패턴이 항상 동일 대체 가능하다는 오해 — 자식에게 노출되는 ref 는 여전히 필요.