fix(过程详情): 修复练习模式输入框完全无法输入的问题
根本原因: 1. getProcessDetail(id) 每次渲染产生新对象 → practiceItems/currentPracticeItem 引用不稳定 → reset useEffect 每次渲染触发 → userInput 被立即清空 2. exitPractice 定义在 validateInput 之后 → 闭包捕获到 undefined 修复: - 用 useMemo([id]) 稳定 processDetail 引用 - 将 exitPractice 移至 validateInput 之前定义 - reset useEffect 依赖改为 [isPracticeMode, currentPracticeId, currentPracticeNameLength] 使用原始类型值避免对象引用不稳定触发误重置 via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
@@ -22,7 +22,7 @@ export function ProcessDetailPage() {
|
||||
const { id } = useParams()
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const processDetail = id ? getProcessDetail(id) : null
|
||||
const processDetail = useMemo(() => (id ? getProcessDetail(id) : null), [id])
|
||||
|
||||
// ITTO 显示/隐藏状态管理
|
||||
const [visible, setVisible] = useState<Record<IttoSection, boolean>>(() => {
|
||||
@@ -124,11 +124,39 @@ export function ProcessDetailPage() {
|
||||
return () => { clearLongPressTimer(); clearAutoAdvanceTimer() }
|
||||
}, [clearLongPressTimer, clearAutoAdvanceTimer])
|
||||
|
||||
useEffect(() => { latestInputRef.current = userInput }, [userInput])
|
||||
|
||||
// ── 开始 / 退出练习(必须在 validateInput 之前定义,避免闭包捕获 undefined)──
|
||||
const exitPractice = useCallback(() => {
|
||||
clearAutoAdvanceTimer()
|
||||
clearLongPressTimer()
|
||||
setIsPracticeMode(false)
|
||||
setAnsweredItems(new Set())
|
||||
setCurrentPracticeId(null)
|
||||
setUserInput([])
|
||||
setCharStatuses([])
|
||||
setIsComposing(false)
|
||||
isComposingRef.current = false
|
||||
setLastErrorTimestamp(null)
|
||||
setShowAnswer(false)
|
||||
setInputLocked(false)
|
||||
}, [clearAutoAdvanceTimer, clearLongPressTimer])
|
||||
|
||||
const startPractice = useCallback(() => {
|
||||
if (practiceItems.length === 0) return
|
||||
clearAutoAdvanceTimer()
|
||||
setIsPracticeMode(true)
|
||||
setAnsweredItems(new Set())
|
||||
setCurrentPracticeId(practiceItems[0].id)
|
||||
}, [practiceItems, clearAutoAdvanceTimer])
|
||||
|
||||
// 切换到某个练习项时重置输入状态,并聚焦第一个输入框
|
||||
// 依赖 currentPracticeId 和名称长度,而非 currentPracticeItem 对象引用(避免每次渲染重置)
|
||||
const currentPracticeNameLength = currentPracticeItem?.name.length ?? 0
|
||||
useEffect(() => {
|
||||
if (!isPracticeMode || !currentPracticeItem) return
|
||||
setUserInput(new Array(currentPracticeItem.name.length).fill(''))
|
||||
setCharStatuses(new Array(currentPracticeItem.name.length).fill('pending'))
|
||||
if (!isPracticeMode || !currentPracticeId || currentPracticeNameLength === 0) return
|
||||
setUserInput(new Array(currentPracticeNameLength).fill(''))
|
||||
setCharStatuses(new Array(currentPracticeNameLength).fill('pending'))
|
||||
setLastErrorTimestamp(null)
|
||||
setShowAnswer(false)
|
||||
setInputLocked(false)
|
||||
@@ -139,9 +167,7 @@ export function ProcessDetailPage() {
|
||||
) as HTMLInputElement
|
||||
firstInput?.focus()
|
||||
}, 150)
|
||||
}, [isPracticeMode, currentPracticeItem])
|
||||
|
||||
useEffect(() => { latestInputRef.current = userInput }, [userInput])
|
||||
}, [isPracticeMode, currentPracticeId, currentPracticeNameLength])
|
||||
|
||||
// 切换过程时自动退出练习(避免定时器跨过程触发)
|
||||
useEffect(() => {
|
||||
@@ -183,7 +209,7 @@ export function ProcessDetailPage() {
|
||||
setLastErrorTimestamp(Date.now())
|
||||
}
|
||||
},
|
||||
[currentPracticeItem, currentPracticeIndex, practiceItems, clearAutoAdvanceTimer]
|
||||
[currentPracticeItem, currentPracticeIndex, practiceItems, clearAutoAdvanceTimer, exitPractice]
|
||||
)
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
@@ -251,30 +277,6 @@ export function ProcessDetailPage() {
|
||||
setInputLocked(false)
|
||||
}, [clearLongPressTimer])
|
||||
|
||||
// ── 开始 / 退出练习 ───────────────────────────────────────────────────────
|
||||
const startPractice = useCallback(() => {
|
||||
if (practiceItems.length === 0) return
|
||||
clearAutoAdvanceTimer()
|
||||
setIsPracticeMode(true)
|
||||
setAnsweredItems(new Set())
|
||||
setCurrentPracticeId(practiceItems[0].id)
|
||||
}, [practiceItems, clearAutoAdvanceTimer])
|
||||
|
||||
const exitPractice = useCallback(() => {
|
||||
clearAutoAdvanceTimer()
|
||||
clearLongPressTimer()
|
||||
setIsPracticeMode(false)
|
||||
setAnsweredItems(new Set())
|
||||
setCurrentPracticeId(null)
|
||||
setUserInput([])
|
||||
setCharStatuses([])
|
||||
setIsComposing(false)
|
||||
isComposingRef.current = false
|
||||
setLastErrorTimestamp(null)
|
||||
setShowAnswer(false)
|
||||
setInputLocked(false)
|
||||
}, [clearAutoAdvanceTimer, clearLongPressTimer])
|
||||
|
||||
if (!processDetail) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-20">
|
||||
|
||||
Reference in New Issue
Block a user