리펙토링: 코드 최적화

This commit is contained in:
2026-05-27 18:15:27 +09:00
parent 326da377c9
commit 4d6fb49cba
5 changed files with 30 additions and 91 deletions
+2 -72
View File
@@ -12,12 +12,11 @@ import type {
import FeedbackPanel from "@/components/game/FeedbackPanel";
import ScoreSummary from "@/components/game/ScoreSummary";
import StageIndicator from "@/components/game/StageIndicator";
import { DESKTOP_INPUT_DEVICE_QUERY } from "@/lib/input-device";
import KatexRenderer from "@/components/math/KatexRenderer";
import { useGameStore } from "@/store/gameStore";
import type { ProblemCount } from "@/store/gameStore";
const DESKTOP_INPUT_DEVICE_QUERY = "(any-hover: hover) and (any-pointer: fine)";
const MOBILE_KEYBOARD_SPACER_OFFSET = 480;
const REAL_NUMBER_PATTERN = /^-?\d+(?:\.\d+)?$/;
function parseAnswer(value: string) {
@@ -44,14 +43,11 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
const resetSession = useGameStore((state) => state.resetSession);
const problemCardRef = useRef<HTMLDivElement>(null);
const scrollTimeoutsRef = useRef<number[]>([]);
const viewportResizeCleanupRef = useRef<(() => void) | null>(null);
const answerFormRef = useRef<AnswerFormHandle>(null);
const [answerA, setAnswerA] = useState("");
const [answerB, setAnswerB] = useState("");
const [activeInput, setActiveInput] = useState<ActiveAnswerInput>(0);
const [mobileFocusSpacerHeight, setMobileFocusSpacerHeight] = useState(0);
const [inputError, setInputError] = useState("");
const sessionProblemCount = session?.problems.length ?? null;
@@ -89,17 +85,6 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
setInputError("");
}, [currentProblem?.key]);
const clearViewportFocusCleanup = useCallback(() => {
scrollTimeoutsRef.current.forEach((timeoutId) => {
window.clearTimeout(timeoutId);
});
scrollTimeoutsRef.current = [];
viewportResizeCleanupRef.current?.();
viewportResizeCleanupRef.current = null;
}, []);
useEffect(() => clearViewportFocusCleanup, [clearViewportFocusCleanup]);
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
@@ -124,10 +109,6 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
answerFormRef.current?.focusInput();
}
const handleInputBlur = useCallback(() => {
setMobileFocusSpacerHeight(0);
}, []);
const scrollKatexToViewportTop = useCallback(() => {
if (window.matchMedia(DESKTOP_INPUT_DEVICE_QUERY).matches) return;
@@ -157,53 +138,10 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
window.scrollTo(0, scrollTop);
}
const visualViewport = window.visualViewport;
const keyboardHeight = Math.max(
0,
window.innerHeight -
(visualViewport?.height ?? window.innerHeight) -
(visualViewport?.offsetTop ?? 0),
);
setMobileFocusSpacerHeight(
Math.max(
window.innerHeight,
keyboardHeight + MOBILE_KEYBOARD_SPACER_OFFSET,
),
);
clearViewportFocusCleanup();
if (visualViewport) {
let settleTimeoutId: number | null = null;
const handleViewportResize = () => {
if (settleTimeoutId !== null) {
window.clearTimeout(settleTimeoutId);
}
settleTimeoutId = window.setTimeout(() => {
alignKatex();
}, 180);
};
visualViewport.addEventListener("resize", handleViewportResize);
viewportResizeCleanupRef.current = () => {
visualViewport.removeEventListener("resize", handleViewportResize);
if (settleTimeoutId !== null) {
window.clearTimeout(settleTimeoutId);
}
};
}
window.requestAnimationFrame(() => {
window.requestAnimationFrame(alignKatex);
});
[900, 1300].forEach((delay) => {
const timeoutId = window.setTimeout(alignKatex, delay);
scrollTimeoutsRef.current.push(timeoutId);
});
}, [clearViewportFocusCleanup]);
}, []);
if (!session || sessionNeedsInit) {
return (
@@ -284,7 +222,6 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
onActiveInputChange={setActiveInput}
onAnswerAChange={setAnswerA}
onAnswerBChange={setAnswerB}
onInputBlur={handleInputBlur}
onInputFocus={scrollKatexToViewportTop}
onSubmit={handleSubmit}
/>
@@ -298,13 +235,6 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
onNext={handleNextProblem}
/>
) : null}
{!submitted && mobileFocusSpacerHeight > 0 ? (
<div
aria-hidden="true"
style={{ height: mobileFocusSpacerHeight }}
/>
) : null}
</section>
</main>
);