You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 17:40:02