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

React中如何用布局各异的组件实现自动编号的步骤列表

可行实现方案

一共3种生产环境常用的实现,按复杂度从低到高选择即可:


方案1:父组件注入编号(无额外依赖,最稳妥)

如果步骤组件都是步骤容器的直接子节点,完全不需要Context或者HOC,直接在父组件遍历子节点时注入编号属性即可。
核心实现代码:

// 步骤容器组件
const StepContainer = ({ children }) => {
  let stepCount = 0
  // 遍历子节点,给有效Step组件注入number属性
  const renderedChildren = React.Children.map(children, child => {
    if (!React.isValidElement(child) || child.type !== Step) return child
    stepCount += 1
    return React.cloneElement(child, { number: stepCount })
  })
  return <div className="step-list">{renderedChildren}</div>
}

// 单个步骤组件
const Step = ({ number, title, children }) => {
  return (
    <div className="step-item">
      <h3>Step {number}: {title}</h3>
      <div className="step-content">{children}</div>
    </div>
  )
}

使用时直接嵌套即可,编号自动生成:

<StepContainer>
  <Step title="填写基础信息">{/* 第一步自定义内容 */}</Step>
  <Step title="上传材料">{/* 第二步自定义内容 */}</Step>
  <Step title="确认提交">{/* 第三步自定义内容 */}</Step>
</StepContainer>

这个方案的局限是:Step组件必须是StepContainer的直接子节点,如果中间套了<div>、栅格组件这类布局节点,就无法识别注入编号。


方案2:Context + 自动计数HOC(支持嵌套布局,灵活度最高)

如果步骤组件可能被其他布局组件包裹,不是容器的直接子节点,就用Context做跨层级编号传递,配合HOC自动获取编号,不需要手动传属性。
核心实现:

  1. 先创建Context,存储计数递增方法
const StepContext = React.createContext({
  getNextStepNumber: () => 1
})
  1. 改造StepContainer,在组件内部维护计数,提供递增方法
const StepContainer = ({ children }) => {
  const [count, setCount] = React.useState(0)
  const getNextStepNumber = React.useCallback(() => {
    setCount(prev => prev + 1)
    return count + 1
  }, [count])

  return (
    <StepContext.Provider value={{ getNextStepNumber }}>
      <div className="step-list">{children}</div>
    </StepContext.Provider>
  )
}
  1. 编写HOC,给Step组件自动注入编号,组件挂载时自动获取序号
const withStepNumber = (WrappedComponent) => {
  return (props) => {
    const { getNextStepNumber } = React.useContext(StepContext)
    const numberRef = React.useRef(null)
    // 首次渲染时获取编号,避免重渲染导致序号错乱
    if (numberRef.current === null) {
      numberRef.current = getNextStepNumber()
    }
    return <WrappedComponent {...props} number={numberRef.current} />
  }
}

// 用HOC包装Step组件
const Step = withStepNumber(({ number, title, children }) => {
  return (
    <div className="step-item">
      <h3>Step {number}: {title}</h3>
      <div className="step-content">{children}</div>
    </div>
  )
})

这种方案下,哪怕Step组件被多层布局包裹,编号也能正常生成,比如下面的写法完全可以正常运行:

<StepContainer>
  <div className="row">
    <div className="col">
      <Step title="填写基础信息">{/* 内容 */}</Step>
    </div>
  </div>
  <Grid>
    <Grid.Col>
      <Step title="上传材料">{/* 内容 */}</Step>
    </Grid.Col>
  </Grid>
  <Step title="确认提交">{/* 内容 */}</Step>
</StepContainer>

注意不要用模块级别的全局变量存计数,否则同一个页面渲染多个StepContainer的时候,序号会互相干扰,出现跨容器累加的问题。以上两种方案的计数都存在组件实例内部,多实例渲染不会有冲突。


方案3:CSS计数器(零JS逻辑,纯样式实现)

如果不需要在JS逻辑里拿到步骤编号,只是要在标题上显示Step #:的文本,甚至可以不用写JS计数逻辑,直接用CSS的counter-reset和counter-increment实现,代码量最少。
核心代码:

.step-list {
  counter-reset: step-counter; /* 初始化计数器 */
}
.step-item {
  counter-increment: step-counter; /* 每个步骤项计数器+1 */
}
.step-title::before {
  content: "Step " counter(step-counter) ": "; /* 自动生成编号前缀 */
}

对应的JSX只需要正常写结构,不需要传任何编号相关属性:

<div className="step-list">
  <div className="step-item">
    <h3 className="step-title">填写基础信息</h3>
    {/* 内容 */}
  </div>
  {/* 其他步骤项结构一致 */}
</div>

这个方案最轻量,但缺点是编号只存在于CSS渲染层,JS逻辑里拿不到对应序号,如果后续需要做步骤跳转、步骤校验这类依赖编号的逻辑,扩展成本很高。


选型参考

  • 步骤都是直接子节点、逻辑简单选方案1,代码少坑少
  • 布局嵌套多、后续可能扩展步骤相关逻辑选方案2,灵活度最高
  • 纯展示需求、不需要JS操作编号选方案3,几行CSS就能搞定

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 09:48:15