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

React StatusStepper组件左右箭头按需显示失效求助

问题分析

当前实现的核心问题在于:

  • 仅在组件挂载和窗口 resize 时检查箭头状态,但未监听 stepper 的滚动事件,用户手动滚动后箭头状态不会更新;
  • 依赖项缺失:steps、activeStep 变化时,stepper 的内容宽度或位置可能改变,但当前没有触发重新检查箭头;
  • 初始检查时机可能过早:组件挂载时 DOM 可能还未完全渲染,stepperRef.current 的尺寸计算不准确。
修复方案
  1. 添加滚动监听:给 stepperRef.current 添加 scroll 事件监听,滚动时实时更新箭头状态;
  2. 补全 useEffect 依赖项:将 steps、activeStep 加入依赖数组,确保这些 props 变化时重新检查箭头;
  3. 优化初始检查时机:使用 useLayoutEffect 替代第一个 useEffect,确保 DOM 渲染完成后再执行初始检查;
  4. 合并重复的 useEffect:把挂载、resize、滚动监听的逻辑合并到同一个 useEffect 中,避免冗余。
修改后的完整代码
import { useRef, useState, useLayoutEffect } from 'react';
import { ArrowBackIosIcon, ArrowForwardIosIcon } from '@mui/icons-material';

export const StatusStepper = ({ activeStep, steps, handleStepClick }) => {
    const stepperRef = useRef(null);
    const [showLeftArrow, setShowLeftArrow] = useState(false);
    const [showRightArrow, setShowRightArrow] = useState(false);

    const checkArrows = () => {
        if (!stepperRef.current) return;
        const { scrollLeft, scrollWidth, clientWidth } = stepperRef.current;
        setShowLeftArrow(scrollLeft > 0);
        // 增加微小容差,避免浮点计算误差导致箭头显示异常
        setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 1);
    };

    const scrollStepper = (direction) => {
        if (!stepperRef.current) return;
        const stepWidth = stepperRef.current.clientWidth / steps.length;
        const scrollAmount = direction === 'left' ? -stepWidth : stepWidth;
        stepperRef.current.scrollBy({
            left: scrollAmount,
            behavior: 'smooth',
        });
    };

    useLayoutEffect(() => {
        const stepper = stepperRef.current;
        if (!stepper) return;

        // 初始检查
        checkArrows();

        // 监听滚动和resize事件
        const handleScroll = checkArrows;
        const handleResize = checkArrows;

        stepper.addEventListener('scroll', handleScroll);
        window.addEventListener('resize', handleResize);

        // 清理函数
        return () => {
            stepper.removeEventListener('scroll', handleScroll);
            window.removeEventListener('resize', handleResize);
        };
    }, [steps, activeStep]); // 依赖steps和activeStep,确保内容变化时重新检查

    return (
        <div className="stepper-container">
            {showLeftArrow && (
                <div className="arrow-container" onClick={() => scrollStepper('left')}>
                    <ArrowBackIosIcon />
                </div>
            )}
            <div className="stepper-content" ref={stepperRef}>
                <div className="breadcrumb">
                    {steps.map((step, index) => (
                        <a
                            key={index}
                            href="#"
                            onClick={(e) => {
                                e.preventDefault(); // 阻止默认锚点跳转
                                handleStepClick(index, step.status);
                            }}
                            className={`${activeStep === index ? 'active' : ''}`}
                            style={{ cursor: 'pointer' }}
                        >
                            <span className="breadcrumb__inner">
                                <span className="breadcrumb__title">{step.title}</span>
                                {/* 注释的badge部分保留原样 */}
                                {/* <span className="badge" style={{
                                    backgroundColor: activeStep === index ? '#fff' : '#255F79',
                                    color: activeStep === index ? '#255F79' : '#fff',
                                    fontSize: '0.75rem',
                                }}>
                                    {step.counts}
                                </span> */}
                            </span>
                        </a>
                    ))}
                </div>
            </div>
            {showRightArrow && (
                <div className="arrow-container" onClick={() => scrollStepper('right')}>
                    <ArrowForwardIosIcon />
                </div>
            )}
        </div>
    );
};
额外优化点
  • 在 checkArrows 中增加了1px的容差,避免因浏览器浮点计算误差导致箭头在应该隐藏时仍显示;
  • 给锚点标签添加了 e.preventDefault(),阻止默认的页面跳转行为;
  • 使用 useLayoutEffect 确保DOM布局完成后再执行初始检查,避免初始状态计算错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 13:07:35