Event와 Effect 분리하기 — useEffectEvent · 퀴즈

7 문항 · Bloom: Understand:2, Apply:3, Analyze:1, Evaluate:1

Q1 Understand mcq_single

동일한 `sendMessage(text)` 호출을 event handler에 둘지 Effect에 둘지 결정하는 가장 본질적인 질문은 무엇인가?

정답: B
S6.C1의 결정 질문은 '왜 실행돼야 하나?' — 인터랙션 응답이면 handler(non-reactive), 표시 중 동기화면 Effect(reactive). 이 한 줄이 위치 결정의 모든 것이다.
오답 해설:
  • A. 비동기 여부는 위치 결정의 기준이 아니다. fetch는 클릭 결과면 handler, 동기화면 Effect 어느 쪽에도 들어갈 수 있다.
  • C. 두 곳 모두 reactive value를 읽을 수 있다. 위치는 '왜 실행되는가'에 따라 갈린다 — 핵심은 트리거의 본질이지 읽기 여부가 아니다.
  • D. 외부 시스템 접근은 Effect의 정의 중 일부지만, handler도 외부 시스템을 호출한다(예: addToCart, sendMessage). 결정 기준은 트리거다.
Q2 Apply mcq_single

다음 중 Event handler가 아니라 Effect에 두는 것이 적절한 시나리오는?

정답: B
B만 '컴포넌트가 화면에 있는 동안 query에 동기화'되는 케이스 — query 변경에 자동 반응해야 하므로 Effect. 나머지는 모두 사용자 인터랙션 결과로 일어나는 일이라 handler.
오답 해설:
  • A. submit은 명백한 인터랙션. Effect로 옮기면 mount/deps 변경 때 의도 없이 발사된다.
  • C. 클릭 결과로 일어나는 한 번의 부수효과 — handler가 정답.
  • D. logout 버튼 누름이라는 인터랙션의 결과. Effect에 두면 의도하지 않은 시점에 토큰이 삭제될 수 있다.
Q3 Understand mcq_multi

react.dev 기준 useEffectEvent의 특성으로 옳은 것을 모두 고르시오. (정답 3개)

정답: A, B, E
useEffectEvent의 핵심 특성은 (1) 항상 최신 값, (2) non-reactive(deps 제외), (3) Effect 안에서만 호출, (4) 외부 전달 금지, 그리고 실험적 API라는 점. A·B·E가 옳다.
오답 해설:
  • C. 제약 1 — Effect 안에서만 호출. 평범한 핸들러 자리에는 그냥 함수를 쓰면 된다.
  • D. 제약 2 — 다른 컴포넌트/Hook에 전달 금지. 넘기면 reactive 흐름에서 분리된 '섬'이 외부로 새어나가 추적 불가능해진다.
Q4 Apply mcq_single

다음 ChatRoom 코드는 다크/라이트 테마를 토글할 때마다 채팅이 끊겼다 다시 연결된다. ```tsx function ChatRoom({ roomId, theme }) { useEffect(() => { const c = createConnection(roomId); c.on('connected', () => showNotification('Connected!', theme)); c.connect(); return () => c.disconnect(); }, [roomId, theme]); } ``` 가장 적절한 리팩터링은?

정답: B
alert/notification 은 'theme를 읽는다 ≠ theme에 반응한다' — non-reactive 로직이다. useEffectEvent로 추출하면 항상 최신 theme로 알림이 뜨면서도 deps에는 빠져 재연결이 사라진다. S6.C2의 핵심 패턴.
오답 해설:
  • A. Lint suppress는 의미 분리가 아닌 우회 — stale closure 위험을 그대로 둔 채 경고만 가린다. 'suppress 대신 useEffectEvent' 원칙 위반.
  • C. ref 우회는 렌더 중 mutation/읽기 안티패턴이 될 수 있고, 의미를 코드로 표현하지 못한다. useEffectEvent가 정확히 이 문제를 위한 도구다.
  • D. 사용자 의도는 '테마 토글로는 연결을 유지'다. 재연결을 정상 동작으로 받아들이면 UX가 망가진다.
Q5 Apply mcq_single

Page 컴포넌트는 `url`이 바뀔 때만 방문 로그를 남기되, 로그에는 항상 최신 장바구니 개수(`numberOfItems`)를 함께 기록해야 한다. '재발화 조건은 의도대로 url에만, numberOfItems는 본체에서 직접' 패턴을 가장 정확히 구현한 코드는?

정답: A
원칙: reactive 값(url)은 인자로 전달하고, non-reactive 값(numberOfItems)은 Effect Event 본체에서 직접 읽는다. deps는 재발화 조건인 [url]만. A가 정확히 이 패턴이다.
오답 해설:
  • B. numberOfItems를 deps에 넣으면 카트 변경마다 재발화한다 — 의도와 정반대. 또한 url을 본체에서 직접 읽는 건 reactive 인자 패턴을 어긴다.
  • C. lint suppress 우회 — stale closure로 numberOfItems가 옛 값으로 기록될 수 있고, 의미가 코드에 드러나지 않는다.
  • D. deps `[]`는 mount 시 한 번만 — url 변경에 재발화하지 않으므로 요구사항 위반.
Q6 Analyze mcq_single

다음 코드의 가장 큰 문제와 올바른 처방을 고르시오. ```tsx function Timer() { const [count, setCount] = useState(0); const onTick = useEffectEvent(() => setCount(c => c + 1)); useTimer(onTick, 1000); // 자식 Hook에 전달 } ```

정답: C
S6.C3 핵심 안티패턴 — '다른 Hook/컴포넌트에 인자로 전달 금지'. 처방은 '사용처 동거': useEffectEvent를 그것이 호출되는 Hook(useTimer) 내부에서 정의한다. 그러면 Timer는 `useTimer(() => setCount(c => c+1), 1000)`로 깔끔해진다.
오답 해설:
  • A. 정확히 제약 2 위반. 자식 Hook은 onTick의 reactive 여부를 모르고 호출 시점이 부모 렌더와 어긋난다.
  • B. setCount가 stable인 건 맞지만, 일반적으로 callback이 reactive 값을 읽는 경우를 다루는 구조 — 사용처-동거 원칙이 더 본질이다.
  • D. onTick은 'on + 사건' 형의 좋은 이름이다. onMount는 오히려 라이프사이클 어휘로 회귀하는 잘못된 명명이다.
Q7 Evaluate mcq_multi

팀 코드 리뷰에서 useEffectEvent 사용에 대한 다음 의견 중 옳은 것을 모두 고르시오. (정답 2개)

정답: A, C
A는 'suppress 대신 useEffectEvent'의 정확한 해석 — 의미 판단이 먼저다. C는 react.dev의 명명 가이드. B는 라이프사이클 어휘로 회귀시키는 잘못된 명명, D는 사실 오류(실험적 API), E는 핸들러 자리에는 그냥 함수를 쓰면 되므로 과사용이다.
오답 해설:
  • B. onMount/onUpdate는 Effect를 라이프사이클 훅으로 사고하게 만들어 멘탈 모델을 오염시킨다 — react.dev가 명시적으로 지양하는 명명.
  • D. useEffectEvent는 React 실험적(experimental) API. stable 출시 전엔 시그니처/import 경로가 바뀔 수 있어 production 도입은 신중해야 한다.
  • E. onClick 같은 핸들러는 이미 인터랙션 시점에만 실행되므로 stale 위험이 없다. useEffectEvent는 'Effect 내부의 non-reactive 조각' 전용 도구다.