如何解决Next.js 13.4+TypeScript简易数学应用的未处理运行时错误
组件功能:生成两个随机数(1-100和1-10),确保前者可被后者整除,验证用户输入的答案是否正确。当前布局使用<html lang='en' suppressHydrationWarning>,仅页面刷新时出现问题,初始渲染正常,不确定是否要改为服务器组件及操作方法。
组件代码
'use client'; import React, { useState } from 'react'; import { randomOneToHundred, randomOneToTen } from './Randoms/randomNumbers'; const DivisionQuestion = () => { const [userAnswer, setUserAnswer] = useState(''); // Перевірка чисел на ділення без остачі const [numberX, setNumberX] = useState<number | null>(null); const [numberY, setNumberY] = useState<number | null>(null); // Функція для перевірки const checkDivision = (a: number, b: number): boolean => { return a % b === 0; }; function checkNumbers() { const randomNum1 = randomOneToHundred(); const randomNum2 = randomOneToTen(); if (checkDivision(randomNum1, randomNum2)) { setNumberX(randomNum1); setNumberY(randomNum2); } else { checkNumbers(); } } if (numberX === null || numberY === null) { checkNumbers(); } const correctAnswer: number = numberX !== null && numberY !== null ? numberX / numberY : 0; const handleAnswerChange = (event: React.ChangeEvent<HTMLInputElement>) => { setUserAnswer(event.target.value); }; const checkAnswer = () => { if (userAnswer === '') { return null; } const userEnteredNumber = parseFloat(userAnswer); if (!isNaN(userEnteredNumber) && userEnteredNumber === correctAnswer) { return <p className='text-green-500'>Відповідь правильна!</p>; } else { return <p className='text-red-500'>Спробуйте ще раз.</p>; } }; return ( <section className=''> <p className='dark:text-white'> Ділення {numberX} / {numberY} </p> <p> Скільки буде {numberX} поділити на {numberY}? </p> <input type='text' value={userAnswer} autoComplete='off' onChange={handleAnswerChange} className=''/ <div className='mt-3'>{checkAnswer()}</div> </section> ); }; export default DivisionQuestion;
问题分析与解决
1. 渲染阶段直接触发状态更新导致的无限循环与Hydration错误
代码中在组件顶层直接判断if (numberX === null || numberY === null) { checkNumbers(); },这会在每次组件渲染时调用checkNumbers,而checkNumbers里的setNumberX/setNumberY又会触发重新渲染,形成无限循环。同时在Next.js的Hydration过程中,服务端渲染时numberX/Y为null,客户端渲染时立刻生成随机数,导致服务端与客户端DOM内容不匹配,刷新时触发Hydration错误。
解决方法:
- 用
useEffect替代顶层判断,只在组件挂载时初始化随机数:
先导入useEffect:
替换原顶层判断逻辑:import React, { useState, useEffect } from 'react';useEffect(() => { checkNumbers(); }, []); - 优化
checkNumbers,避免递归可能导致的栈溢出(极端情况连续生成无法整除的数),改为循环:function checkNumbers() { let randomNum1, randomNum2; do { randomNum1 = randomOneToHundred(); randomNum2 = randomOneToTen(); } while (!checkDivision(randomNum1, randomNum2)); setNumberX(randomNum1); setNumberY(randomNum2); }
2. Hydration不匹配的根源与处理
原代码中初始状态numberX/Y为null,服务端渲染会输出包含null的文本,客户端挂载后立刻替换为随机数,导致服务端与客户端DOM不一致。suppressHydrationWarning只是隐藏警告,并未解决问题。
解决方法:
在numberX/Y未初始化完成时,显示加载状态,避免渲染不一致的内容:
if (numberX === null || numberY === null) { return <p className='dark:text-white'>Завантаження...</p>; }
将这段代码放到return语句之前。
关于服务器组件的说明
这个组件不需要改为服务器组件。因为服务器组件无法处理客户端交互逻辑(比如useState、输入框的onChange事件),而你的组件需要实时响应用户输入、验证答案,必须保留'use client'指令作为客户端组件。
内容的提问来源于stack exchange,提问作者antonkornilov-ua

