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

ReactJS实现Section组件自动计数 解决StrictMode计数翻倍问题

React 自动递增Section序号实现方案

Section自动序号渲染效果

需求说明

需要实现以下能力:

  • 无需手动给Section组件传入序号属性
  • 在Container组件内渲染多个无传参的Section时,自动生成从1开始递增的连续序号
  • 兼容React.StrictMode,不会出现序号翻倍异常

初始手动传参实现(不符合需求)

export const Section = ({ number }) => {
  return <div>Section {number}</div>;
};

export const Container = () => {
  return (
    <div>
      <Section number={1} />
      <Section number={2} />
      <Section number={3} />
      <Section number={4} />
    </div>
  );
};

目标代码结构

export const Container = () => {
  return (
    <div>
      <Section />
      <Section />
      <Section />
      <Section />
    </div>
  );
};

之前Context方案序号翻倍的原因

React.StrictMode在开发环境会重复执行组件渲染、初始化逻辑,用来检测代码中的不规范写法。之前的Context实现出现2、4、6、8的翻倍问题,核心是违反了React的渲染纯函数原则:在组件渲染阶段直接修改Context中的共享计数器变量,重复渲染时会重复执行递增逻辑,最终导致计数异常。

典型错误写法示例:

// 错误:渲染阶段直接修改外部状态
const CountContext = createContext({ count: 0, setCount: () => {} });
const Section = () => {
  const { count, setCount } = useContext(CountContext);
  setCount(count + 1); // 渲染阶段直接更新状态,StrictMode下会重复执行
  return <div>Section {count}</div>;
};

可行实现方案

方案1:Children遍历注入属性(简单场景首选)

如果Section都是Container的直接子元素,这个方案实现最简单、稳定性最高,从根源避免StrictMode兼容问题:

import React from 'react';

export const Section = ({ number }) => {
  return <div>Section {number}</div>;
};

export const Container = ({ children }) => {
  let sectionIndex = 0;
  // 遍历所有子元素,给Section组件自动注入序号
  const renderNodes = React.Children.map(children, (child) => {
    if (child?.type === Section) {
      sectionIndex++;
      return React.cloneElement(child, { number: sectionIndex });
    }
    return child;
  });

  return <div>{renderNodes}</div>;
};

方案特性:

  • 逻辑完全收敛在Container层,无额外状态管理成本
  • 支持Container内混写其他非Section组件,不会打乱计数顺序
  • 完全兼容StrictMode,无重复计数问题
  • 缺点:不支持Section嵌套在Container的深层子节点中(比如中间包裹了div、其他布局组件)

方案2:修正版Context实现(支持深层嵌套场景)

如果需要支持Section在Container下任意层级嵌套,可使用基于注册机制的Context方案,将注册逻辑放在副作用中执行,避免渲染阶段修改状态:

import React, { createContext, useContext, useRef, useLayoutEffect, useState } from 'react';

const SectionContext = createContext(null);

const SectionCounterProvider = ({ children }) => {
  // 用ref存储已注册的Section实例,避免渲染阶段触发状态更新
  const sectionMap = useRef(new Map());
  const [, reRender] = useState({});

  // 注册Section,返回注销方法
  const register = (refId) => {
    sectionMap.current.set(refId, true);
    reRender({});
    return () => {
      sectionMap.current.delete(refId);
      reRender({});
    };
  };

  // 按注册顺序计算当前Section的序号
  const getNumber = (refId) => {
    return Array.from(sectionMap.current.keys()).indexOf(refId) + 1;
  };

  return (
    <SectionContext.Provider value={{ register, getNumber }}>
      {children}
    </SectionContext.Provider>
  );
};

export const Section = () => {
  const { register, getNumber } = useContext(SectionContext);
  const selfRef = useRef(Symbol());
  const [num, setNum] = useState(0);

  useLayoutEffect(() => {
    // 注册逻辑放在副作用中执行,StrictMode下重复执行时清理函数会自动注销冗余实例
    const unregister = register(selfRef.current);
    setNum(getNumber(selfRef.current));
    return unregister;
  }, [register, getNumber]);

  return <div>Section {num}</div>;
};

export const Container = ({ children }) => {
  return (
    <SectionCounterProvider>
      <div>{children}</div>
    </SectionCounterProvider>
  );
};

方案特性:

  • 支持任意层级嵌套,即使Section包裹在多层布局组件内也能正确计数
  • 注册/注销逻辑放在useLayoutEffect中执行,StrictMode下重复挂载的实例会被清理函数正确移除,不会出现计数翻倍
  • 支持动态增删Section,序号会自动更新

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 12:18:16