Effect 안 써도 되는 경우 — 12가지 안티패턴 진단 · 퀴즈

9 문항 · Bloom: Apply:3, Analyze:2, Evaluate:4

Q1 Analyze mcq_single

다음 코드의 안티패턴은 12가지 분류 중 어디에 해당하는가? ```tsx const [fullName, setFullName] = useState(''); useEffect(() => { setFullName(firstName + ' ' + lastName); }, [firstName, lastName]); ```

정답: B
firstName/lastName은 이미 props/state이고, fullName은 단순 결합으로 render 중에 한 줄로 계산 가능합니다. Effect+state로 보관하면 '렌더 → Effect → setState → 재렌더'의 추가 렌더가 발생하는 #1 derived state 안티패턴입니다.
오답 해설:
  • A. 외부 store(navigator.onLine 등)가 등장하지 않습니다 — 입력은 모두 React state입니다.
  • C. onChange 같은 부모 콜백 호출이 없으므로 #9가 아닙니다.
  • D. fetch나 비동기 응답 처리가 없으므로 race condition과 무관합니다.
Q2 Apply mcq_single

Q1의 코드를 결정 트리에 따라 가장 적절히 리팩터링한 결과는?

정답: B
Q1(render 중 계산 가능?)에서 즉시 종료 — 단순 문자열 결합이므로 변수 한 줄이 정답입니다. useMemo는 '진짜 비싼 계산 + 안정적 deps + 참조 일관성 필요'의 세 조건이 모두 맞을 때만 의미가 있습니다.
오답 해설:
  • A. 문자열 결합은 비싸지 않으므로 useMemo는 코드 복잡도만 늘립니다.
  • C. 리셋이 아니라 단순 파생값입니다. key는 인스턴스 통째 교체용입니다.
  • D. 외부 store가 아닙니다 — React state로부터의 파생값입니다.
Q3 Evaluate mcq_multi

useMemo가 '실제로 의미 있는' 적용 조건을 모두 고르시오. (정답 3개)

정답: A, B, C
useMemo는 (1) 비싼 계산 (2) 안정적 deps (3) 참조 일관성 수요라는 세 조건이 모두 맞을 때만 가치가 있습니다. 비싸지 않은 계산에 두르면 비교 비용·코드 복잡도만 늘고, React Compiler가 자동 메모이즈할 수도 있습니다.
오답 해설:
  • D. 비싸지 않은 계산을 메모이즈하면 비교 비용이 늘고 코드만 복잡해집니다 — 안티패턴입니다.
  • E. 둘 다 보통 안티패턴 후보입니다. '항상 우선'이 아니라 측정 후 결정해야 합니다.
Q4 Evaluate mcq_single

Profile 컴포넌트가 userId가 바뀔 때 comment, draft, scrollPos 등 모든 내부 state를 처음 값으로 되돌려야 한다. 다음 처방 중 가장 적절한 것은?

정답: B
전체 리셋이면 key prop으로 인스턴스를 통째로 갈아끼우는 것이 가장 깔끔합니다. state를 추가해도 자동 리셋되고, 깜빡임 없고, dev 더블 마운트 영향도 받지 않습니다.
오답 해설:
  • A. 한 번 깜빡이는 추가 렌더, dev 더블 마운트, state 추가 시 리셋 누락 위험 — 안티패턴 #3입니다.
  • C. state-during-render는 '부분 조정'에 쓰는 2순위 답입니다. 전체 리셋엔 key가 더 명료합니다.
  • D. useMemo는 값을 리셋하지 않습니다 — state를 변경하지도 않고 의미가 어긋납니다.
Q5 Evaluate mcq_single

items 배열이 바뀔 때 selectedId만 무효화하고 싶다. 가장 이상적인(1순위) 처방은?

정답: C
1순위는 derived value입니다 — selection을 state로 두지 않고 매 렌더 items.find로 도출하면 동기화 자체가 사라집니다. D(state-during-render)는 derived가 어려운 경우의 2순위, A는 안티패턴 #4, B는 부분이 아니라 전체 리셋용입니다.
오답 해설:
  • A. Effect로 setState하는 처방은 깜빡임·이중 호출·확장성 문제로 마지막 후보입니다.
  • B. 부분 조정인데 컴포넌트 정체성을 통째로 갈아끼우면 다른 state까지 의도치 않게 잃습니다.
  • D. 유효하지만 2순위 — derived가 가능한 상황에서는 state를 유지할 이유가 없습니다.
Q6 Analyze mcq_multi

다음 시나리오들 중 'Effect가 아니라 이벤트 핸들러에 둬야 하는' 것을 모두 고르시오. (정답 3개)

정답: A, B, C
A·B·C는 모두 '사용자가 클릭했기 때문에' 트리거되는 인터랙션 결과 — #6 폼 제출, #7 계산 체인, #9 부모 통지. D는 '표시되었기 때문' → mount-only Effect가 적절(분석 로깅), E는 외부 시스템(채팅 서버) 동기화의 정당한 Effect 사용처입니다.
오답 해설:
  • D. '페이지 표시 때문'이라는 트리거이므로 Effect가 정답입니다.
  • E. 외부 시스템(서버) 연결 유지는 Effect의 정당한 사용 — 핸들러에 두면 동기화가 깨집니다.
Q7 Apply mcq_single

다음 안티패턴 코드를 한 핸들러로 합쳐 캐스케이딩 재렌더를 제거하려 한다. 가장 적절한 After는? ```tsx // Before useEffect(() => { if (card.gold) setGoldCount(g => g + 1); }, [card]); useEffect(() => { if (goldCount === 3) { setGoldCount(0); setRound(r => r + 1); } }, [goldCount]); ```

정답: B
안티패턴 #7(계산 체인)의 표준 처방은 한 핸들러에서 다음 상태를 직접 계산해 한 번에 setState하는 것입니다. 1회 렌더로 압축되고, 인과 관계가 명확해지며, dev 더블 마운트의 영향도 없습니다.
오답 해설:
  • A. 한 Effect로 합쳐도 여전히 '표시 트리거'에 의존하는 캐스케이드는 남고, 마운트/리로드에서도 발화합니다.
  • C. round는 누적 게임 상태이지 단순 파생값이 아닙니다 — useMemo로 만들 수 없습니다.
  • D. flushSync는 새 DOM을 즉시 읽어야 하는 경우(focus·scroll)의 처방이지 체인 정리와 무관합니다.
Q8 Apply mcq_single

검색어 query와 page에 따라 결과를 가져오는 다음 코드의 race condition을 가장 표준적으로 제거한 After는? ```tsx // Before useEffect(() => { fetchResults(query, page).then(setResults); }, [query, page]); ```

정답: A
fetch in Effect의 race condition 표준 처방은 ignore flag cleanup입니다. cleanup이 ignore=true로 스위치를 켜서 늦게 도착한 응답을 폐기 — 'a'/'ab'/'abc' 빠르게 입력해도 stale 결과가 화면에 박히지 않습니다. 다음 단계로 useData 커스텀 훅 캡슐화가 옵니다.
오답 해설:
  • B. deps []는 query/page가 바뀌어도 재요청하지 않아 검색 자체가 동작하지 않습니다.
  • C. useMemo는 Promise를 캐시할 뿐 해결되지 않은 비동기 race를 풀어주지 않으며, 렌더 중 fetch는 React 모델 위반입니다.
  • D. useSyncExternalStore는 즉시 동기로 snapshot을 반환해야 합니다 — 비동기 fetch 결과는 그 모델에 맞지 않습니다.
Q9 Evaluate short_answer

다음 세 시나리오 각각에 대해, S4의 결정 트리(Q1 render 중 계산 / Q2 useMemo / Q3 핸들러 / Q4 key·state-during-render·derived / Q5 useSyncExternalStore·useData) 중 어떤 도구를 선택할지와 그 이유를 한 문장씩 정당화하시오. (1) 헤더와 푸터 양쪽에서 navigator.onLine을 표시한다. (2) 메뉴 항목을 클릭하면 라우트를 변경하고 동시에 분석 이벤트를 보낸다. (3) 정렬 기준 sort prop이 바뀔 때 페이지 번호 page만 1로 되돌린다.

모범답안: (1) **useSyncExternalStore** — navigator.onLine은 React 외부 store. 두 컴포넌트가 같은 hook(useOnlineStatus)을 호출하면 React가 tearing 없이 일관성을 보장하고, getServerSnapshot으로 SSR도 안전. 수동 useEffect+addEventListener는 #11 안티패턴. (2) **이벤트 핸들러** — 둘 다 '메뉴 클릭 때문에' 발생하는 인터랙션 결과. 한 핸들러에서 navigate(...)와 trackEvent(...)를 함께 호출. 분석을 Effect에 두면 리로드/뒤로가기 같은 표시 트리거에서도 발화하는 #6 안티패턴. (3) **state-during-render(2순위)** 또는 **derived(가능하면 1순위)** — 부분 조정. const [prevSort, setPrevSort] = useState(sort); if (sort !== prevSort) { setPrevSort(sort); setPage(1); }. page를 derived로 만들 수 없다면(사용자가 직접 페이지를 넘기는 state) state-during-render가 적절. Effect로 setPage(1)을 부르면 #4 안티패턴.채점 기준:
  • criteria
  • full_credit
  • partial_credit