Ref로 값 참조하기 — 재렌더 없이 기억하기 · 퀴즈

8 문항 · Bloom: Understand:2, Apply:2, Analyze:4

Q1 Understand mcq_single

다음 중 useRef가 반환하는 객체와 그 동작을 가장 정확하게 설명한 것은?

정답: B
useRef는 `{ current }` 형태의 객체를 매 렌더마다 동일하게 돌려준다. 직접 mutation이 가능하고 setter는 없으며, 값이 바뀌어도 재렌더를 트리거하지 않는다.
오답 해설:
  • A. setter가 있는 형태는 useState다. ref는 setter 없이 직접 대입한다.
  • C. 흔한 오개념. ref 객체는 한 번 만들어진 뒤 렌더 사이 그대로 재사용된다.
  • D. ref의 가장 중요한 성질(재렌더 미트리거)을 정반대로 설명한 것이다.
Q2 Analyze mcq_multi

useState와 useRef를 비교한 다음 진술 중 옳은 것을 모두 고르시오. (정답 3개)

정답: A, B, C
state vs ref는 (1) 재렌더 트리거 여부, (2) mutation 방식, (3) 렌더 중 읽기 안전성 세 축으로 갈린다. A·B·C는 그 세 축을 정확히 기술한다.
오답 해설:
  • D. 둘 다 렌더 사이 값이 보존된다. 이는 공통점이지 차이점이 아니다.
  • E. ref는 인스턴스마다 자기만의 `{ current }`를 가진다. 공유되는 것은 모듈 전역 변수다.
Q3 Analyze mcq_single

Stopwatch 컴포넌트에서 (1) 화면에 표시되는 경과 시간, (2) setInterval이 반환한 인터벌 ID, (3) 시작 시각을 보관하려고 한다. 각 데이터를 state와 ref 중 어디에 두는 것이 적합한가?

정답: A
판별 규칙: 화면에 보이면 state, 영향 없으면 ref. 경과 시간과 시작 시각은 표시·계산에 쓰이므로 state, 인터벌 ID는 cancel용 핸들이라 화면과 무관하므로 ref다.
오답 해설:
  • B. 표시되는 값을 ref에 두면 클릭/tick에도 화면이 안 바뀐다(stale 표시).
  • C. 인터벌 ID를 state에 두면 setter 호출이 또 다른 재렌더를 일으켜 비효율적이다.
  • D. 표시 값까지 ref로 두면 화면이 갱신되지 않는다.
Q4 Analyze mcq_single

다음 코드의 문제는 무엇인가? ```js function Counter() { const countRef = useRef(0); function handleClick() { countRef.current++; } return <button onClick={handleClick}>You clicked {countRef.current} times</button>; } ```

정답: B
ref는 mutation해도 재렌더를 트리거하지 않는다. 표시되는 값을 ref에 두면 클릭으로 값이 바뀌어도 화면은 갱신되지 않는 stale 표시 안티패턴이 된다. 처방은 useState로 옮기는 것.
오답 해설:
  • A. 이벤트 핸들러에서 ref를 mutation하는 것은 정상적인 사용처다.
  • C. useRef의 초기값은 최초 한 번만 적용된다. ref 객체는 렌더 사이 동일하게 유지된다.
  • D. Strict mode는 컴포넌트 함수를 두 번 호출하지만, 이벤트 핸들러는 두 번 실행되지 않는다.
Q5 Apply mcq_single

다음 중 useRef가 적합한 시나리오로 가장 타당한 것은?

정답: B
timeout ID는 cancel용 핸들일 뿐 화면에 표시되지 않으므로 비-렌더 데이터다. 인스턴스별로 격리되어야 하고 렌더 사이 보존되어야 하므로 정확히 ref의 자리다.
오답 해설:
  • A. 검색어는 화면에 보이고 필터 결과를 다시 그려야 하므로 state다.
  • C. 활성 탭 인덱스가 바뀌면 스타일이 다시 그려져야 하므로 state다.
  • D. 뱃지 숫자는 표시 값이므로 state다.
Q6 Understand true_false

비동기 콜백(예: setTimeout 콜백) 안에서 항상 최신 입력값을 읽으려면, state와 별도로 같은 값을 ref에 동기 유지(`textRef.current = e.target.value`)하는 패턴을 사용할 수 있다.

정답: A
state만 사용하면 setTimeout 콜백은 등록 시점의 클로저에 갇힌 옛 snapshot을 읽는다. setText와 함께 textRef.current에도 같은 값을 쓰면 콜백이 ref를 통해 최신 값을 볼 수 있다. 단, setter 호출과 ref 대입은 항상 짝으로 다녀야 한다.
오답 해설:
  • B. 이 패턴은 클래스 노트 [S1.C2]에서 명시적으로 다룬 정당한 사용 사례다.
Q7 Apply short_answer

다음 코드는 모든 인스턴스가 같은 timeout을 공유해 디바운싱이 충돌한다. 인스턴스별 격리를 회복하도록 useRef로 리팩터링한 코드를 작성하시오. ```js let timeoutID; function DebouncedButton({ onClick, children }) { return ( <button onClick={() => { clearTimeout(timeoutID); timeoutID = setTimeout(onClick, 1000); }}>{children}</button> ); } ```

정답: see rubric
표준 처방: (1) 모듈 전역 `let timeoutID;` 제거, (2) 컴포넌트 본문 안에 `const timeoutRef = useRef(null);` 추가, (3) 모든 `timeoutID` 참조를 `timeoutRef.current`로 치환. 그러면 각 인스턴스가 자기 `{ current }`를 가져 cancel이 충돌하지 않는다.채점 기준:
  • full_credit
  • partial_credit
  • no_credit
Q8 Analyze mcq_single

다음 코드 조각에서 안티패턴을 가장 정확하게 지적한 것은? ```js function ThemedView({ children }) { const modeRef = useRef('light'); if (modeRef.current === 'dark') return <DarkLayout>{children}</DarkLayout>; return <LightLayout>{children}</LightLayout>; } ```

정답: B
렌더 본문에서 ref.current를 읽어 분기하면, ref가 mutable이라 React가 보장하는 결정성(같은 props·state면 같은 출력)이 깨진다. 화면 분기 기준이 되는 값은 state여야 한다(useRef → useState로 변경).
오답 해설:
  • A. useRef의 초기값으로 문자열을 전달하는 것은 정상이다. string ref 경고는 별개 개념(legacy ref API)이다.
  • C. 조건부 반환 자체는 React에서 정상적인 패턴이다. 문제는 조건의 출처가 ref라는 점이다.
  • D. useMemo는 비싼 계산 결과를 메모하는 도구이지 mutable 보관소가 아니다. 처방은 useState다.