安装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'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;
报错截图

错误原因分析
- 类型定义不兼容:
completed状态的类型声明{[k: number]: boolean;}与初始值{}存在类型推断冲突,TypeScript无法确认对象键的类型匹配。 - 事件处理函数类型不匹配:
handleStep返回的函数类型与StepButton的onClick期望的MouseEventHandler类型不兼容。 - 状态修改违反不可变性:
handleComplete中直接修改原状态对象completed,未创建新对象,导致React无法检测状态变化,同时触发类型检查错误。 - 越界判断错误:
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
相关产品推荐
相关产品推荐

