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

安装Material UI后使用Stepper组件遇TypeScript报错求助

Material UI Stepper组件报错排查与修复

问题概述

首次使用Material UI,安装完成后集成Stepper组件时出现持续报错,代码编辑器及浏览器控制台均有错误提示,核心报错位置为:

  • useState定义{[k: number]: boolean;}的类型声明处
  • const handleStep = (step: number)的参数类型处

原代码

import React, { useEffect, useState } from "react";
import Box from '@mui/material/Box';
import Stepper from '@mui/material/Stepper';
import Step from '@mui/material/Step';
import StepButton from '@mui/material/StepButton';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';

const steps = ['Select campaign settings', 'Create an ad group', 'Create an ad'];

const ForgotPassword = () => {
const [activeStep, setActiveStep] = useState(0);
  const [completed, setCompleted] = useState<{
    [k: number]: boolean;
  }>({});

  const totalSteps = () => {
    return steps.length;
  };

  const completedSteps = () => {
    return Object.keys(completed).length;
  };

  const isLastStep = () => {
    return activeStep === totalSteps() - 1;
  };

  const allStepsCompleted = () => {
    return completedSteps() === totalSteps();
  };

  const handleNext = () => {
    const newActiveStep =
      isLastStep() && !allStepsCompleted()
        ? // It's the last step, but not all steps have been completed,
          // find the first step that has been completed
          steps.findIndex((step, i) => !(i in completed))
        : activeStep + 1;
    setActiveStep(newActiveStep);
  };

  const handleBack = () => {
    setActiveStep((prevActiveStep) => prevActiveStep - 1);
  };

  const handleStep = (step: number) => () => {
    setActiveStep(step);
  };

  const handleComplete = () => {
    const newCompleted = completed;
    newCompleted[activeStep] = true;
    setCompleted(newCompleted);
    handleNext();
  };

  const handleReset = () => {
    setActiveStep(0);
    setCompleted({});
  };

  return (
    <>
    <Box sx={{ width: '100%' }}>
      <Stepper nonLinear activeStep={activeStep}>
        {steps.map((label, index) => (
          <Step key={label} completed={completed[index]}>
            <StepButton color="inherit" onClick={handleStep(index)}>
              {label}
            </StepButton>
          </Step>
        ))}
      </Stepper>
      <div>
        {allStepsCompleted() ? (
          <React.Fragment>
            <Typography sx={{ mt: 2, mb: 1 }}>
              All steps completed - you&apos;re finished
            </Typography>
            <Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
              <Box sx={{ flex: '1 1 auto' }} />
              <Button onClick={handleReset}>Reset</Button>
            </Box>
          </React.Fragment>
        ) : (
          <React.Fragment>
            <Typography sx={{ mt: 2, mb: 1, py: 1 }}>
              Step {activeStep + 1}
            </Typography>
            <Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
              <Button
                color="inherit"
                disabled={activeStep === 0}
                onClick={handleBack}
                sx={{ mr: 1 }}
              >
                Back
              </Button>
              <Box sx={{ flex: '1 1 auto' }} />
              <Button onClick={handleNext} sx={{ mr: 1 }}>
                Next
              </Button>
              {activeStep !== steps.length &&
                (completed[activeStep] ? (
                  <Typography variant="caption" sx={{ display: 'inline-block' }}>
                    Step {activeStep + 1} already completed
                  </Typography>
                ) : (
                  <Button onClick={handleComplete}>
                    {completedSteps() === totalSteps() - 1
                      ? 'Finish'
                      : 'Complete Step'}
                  </Button>
                ))}
            </Box>
          </React.Fragment>
        )}
      </div>
    </Box>
 </>
  );
};

export default ForgotPassword;

报错截图

报错截图

错误原因分析

  1. 类型定义不兼容:completed状态的类型声明{[k: number]: boolean;}与初始值{}存在类型推断冲突,TypeScript无法确认对象键的类型匹配。
  2. 事件处理函数类型不匹配:handleStep返回的函数类型与StepButton的onClick期望的MouseEventHandler类型不兼容。
  3. 状态修改违反不可变性:handleComplete中直接修改原状态对象completed,未创建新对象,导致React无法检测状态变化,同时触发类型检查错误。
  4. 越界判断错误:activeStep !== steps.length的条件会导致访问completed[steps.length],出现数组越界,应该改为activeStep !== steps.length - 1。

修复后的代码

import React, { useState } from "react";
import Box from '@mui/material/Box';
import Stepper from '@mui/material/Stepper';
import Step from '@mui/material/Step';
import StepButton from '@mui/material/StepButton';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';

const steps = ['Select campaign settings', 'Create an ad group', 'Create an ad'];

const ForgotPassword = () => {
  const [activeStep, setActiveStep] = useState<number>(0);
  // 明确类型并确保初始值符合类型要求
  const [completed, setCompleted] = useState<Record<number, boolean>>({});

  const totalSteps = () => steps.length;

  const completedSteps = () => Object.keys(completed).length;

  const isLastStep = () => activeStep === totalSteps() - 1;

  const allStepsCompleted = () => completedSteps() === totalSteps();

  const handleNext = () => {
    const newActiveStep = isLastStep() && !allStepsCompleted()
      ? steps.findIndex((_, i) => !(i in completed))
      : activeStep + 1;
    setActiveStep(newActiveStep);
  };

  const handleBack = () => {
    setActiveStep(prev => prev - 1);
  };

  // 明确事件处理函数类型,匹配StepButton的onClick要求
  const handleStep = (step: number) => (e: React.MouseEvent<HTMLButtonElement>) => {
    setActiveStep(step);
  };

  const handleComplete = () => {
    // 采用不可变方式更新状态
    setCompleted(prev => ({
      ...prev,
      [activeStep]: true
    }));
    handleNext();
  };

  const handleReset = () => {
    setActiveStep(0);
    setCompleted({});
  };

  return (
    <>
      <Box sx={{ width: '100%' }}>
        <Stepper nonLinear activeStep={activeStep}>
          {steps.map((label, index) => (
            <Step key={label} completed={completed[index] || false}>
              <StepButton color="inherit" onClick={handleStep(index)}>
                {label}
              </StepButton>
            </Step>
          ))}
        </Stepper>
        <div>
          {allStepsCompleted() ? (
            <React.Fragment>
              <Typography sx={{ mt: 2, mb: 1 }}>
                所有步骤已完成 - 流程结束
              </Typography>
              <Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
                <Box sx={{ flex: '1 1 auto' }} />
                <Button onClick={handleReset}>重置</Button>
              </Box>
            </React.Fragment>
          ) : (
            <React.Fragment>
              <Typography sx={{ mt: 2, mb: 1, py: 1 }}>
                第 {activeStep + 1} 步
              </Typography>
              <Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
                <Button
                  color="inherit"
                  disabled={activeStep === 0}
                  onClick={handleBack}
                  sx={{ mr: 1 }}
                >
                  返回
                </Button>
                <Box sx={{ flex: '1 1 auto' }} />
                <Button onClick={handleNext} sx={{ mr: 1 }}>
                  下一步
                </Button>
                {/* 修正越界判断条件 */}
                {activeStep !== steps.length - 1 &&
                  (completed[activeStep] ? (
                    <Typography variant="caption" sx={{ display: 'inline-block' }}>
                      第 {activeStep + 1} 步已完成
                    </Typography>
                  ) : (
                    <Button onClick={handleComplete}>
                      {completedSteps() === totalSteps() - 1
                        ? '完成'
                        : '完成当前步骤'}
                    </Button>
                  ))}
              </Box>
            </React.Fragment>
          )}
        </div>
      </Box>
    </>
  );
};

export default ForgotPassword;

关键修改说明

  • 将completed的类型改为Record<number, boolean>,更符合TypeScript的类型规范,同时避免初始值的类型冲突。
  • 为handleStep的返回函数添加React.MouseEvent参数类型,确保与StepButton的onClick事件类型匹配。
  • 在handleComplete中使用扩展运算符创建新的状态对象,遵循React状态不可变原则,确保状态能正确更新。
  • 修正条件判断activeStep !== steps.length为activeStep !== steps.length - 1,避免数组越界访问。
  • 在Step组件的completed属性中添加|| false,避免未定义值导致的类型警告。

内容的提问来源于stack exchange,提问作者Adex

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:45:06