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

Vue渲染时递增响应式state触发重渲染循环的原因排查

死循环触发原因

Vue的模板渲染过程中,所有绑定的表达式、方法调用都会在每次重渲染时全量执行。你在getNumber这个渲染阶段调用的方法里修改了响应式状态decrementAmount,会触发下面的连锁反应:

  1. 首次渲染启动,遍历sections逐个调用getNumber计算传入组件的number属性
  2. 遍历到第一个不参与计数的区块时,执行this.decrementAmount++,响应式状态变更被Vue捕获
  3. Vue检测到组件依赖的响应式数据变化,会丢弃当前未完成的渲染,调度一次新的组件重渲染
  4. 新的重渲染启动后,又从头遍历sections调用getNumber,此时decrementAmount已经不是初始值0,再次遇到非计数区块时继续累加,又触发新的重渲染
  5. 上述流程反复触发,直到达到Vue的重渲染递归上限才停止,此时decrementAmount已经被累加了数百次,计算出的序号自然会出现负值这类错误。

你给decrementAmount写固定值时不会触发循环,本质是因为渲染过程中没有修改响应式状态,不会触发新的重渲染调度,但固定值无法动态适配不同页面非计数区块的数量,不符合业务需求。

正确实现方案

核心原则:渲染阶段执行的方法不能有修改响应式状态的副作用,序号计算要做成纯逻辑,不要依赖渲染过程中累加的响应式变量。
最稳妥的实现是提前把序号算好,不要等渲染的时候边渲染边改值。

推荐方案:计算属性预处理区块列表

把序号计算逻辑放到计算属性中,仅在sections数据变化时重新计算一次,渲染时直接读取预处理好的结果,完全没有副作用。
修改后的代码如下:

  1. 调整模板,直接遍历预处理后的列表,不需要在模板里调用计算序号的方法:
<template>
  <div class="sections">
    <component
      :is="getSection(section.__component)"
      v-for="(section, index) in processedSections"
      :key="index"
      :section="section.rawData"
      :number="section.number"
      class="section"
    />
  </div>
</template>
  1. 移除data中不需要的decrementAmount字段,新增计算属性processedSections:
export default {
  name: "SectionStructure",
  components: {
    HeroStyle1: () => import("~/components/hero/HeroStyle1.vue"),
    CommunityBlock: () => import("~/components/sections/CommunityBlock.vue"),
    FiftyFiftyMedia: () => import("~/components/sections/FiftyFiftyMedia.vue"),
    FullscreenGame: () => import("~/components/sections/FullscreenGame.vue"),
    GameRow: () => import("~/components/sections/GameRow.vue"),
    NewsBlock: () => import("~/components/sections/NewsBlock.vue"),
    TitleSideImage: () => import("~/components/sections/TitleSideImage.vue"),
    TwoImagesCta: () => import("~/components/sections/TwoImagesCTA.vue"),
    CareerList: () => import("~/components/sections/CareerList.vue"),
  },
  props: {
    sections: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      allowedNumerableSections: [
        "CommunityBlock",
        "FiftyFiftyMedia",
        "FullscreenGame",
        "GameRow",
        "NewsBlock",
        "TitleSideImage",
        "TwoImagesCta",
      ],
    };
  },
  computed: {
    processedSections() {
      // 计数器放在计算属性内部,非响应式,修改不会触发重渲染
      let count = 0
      return this.sections.map(section => {
        // 组件名转换逻辑和原有逻辑一致
        let component = this.getSection(section.__component);
        component = component.replace(/-([a-z])/g, function (g) {
          return g[1].toUpperCase();
        });
        component = component.charAt(0).toUpperCase() + component.slice(1);

        let number = false
        if (this.allowedNumerableSections.includes(component)) {
          count++
          number = count < 10 ? `/0${count}` : `/${count}`
        }

        return {
          ...section,
          rawData: section,
          number
        }
      })
    }
  },
  methods: {
    getSection(str) {
      return str.substring(str.indexOf(".") + 1);
    },
  },
}

这个方案下,序号是按列表顺序逐一枚举的,遇到非计数区块计数器不增长,天然能保证序号连续,而且计算逻辑只在sections更新时触发,不会产生额外重渲染。

备选方案:非响应式变量计数

如果不想预处理列表,也可以把计数变量改成非响应式的(不要放在data里),这样修改变量不会触发Vue的重渲染。注意需要在每次渲染前重置计数器,避免多次渲染累加出错:

export default {
  // ...组件名、组件引入、props、data配置保持不变,移除data中的decrementAmount
  created() {
    // 非响应式计数器,挂在实例上但不交给Vue托管
    this._numerableCount = 0
  },
  beforeUpdate() {
    // 每次重渲染前重置计数器,从头开始计数
    this._numerableCount = 0
  },
  methods: {
    getSection(str) {
      return str.substring(str.indexOf(".") + 1);
    },
    getNumber(section) {
      let component = this.getSection(section.__component);
      component = component.replace(/-([a-z])/g, function (g) {
        return g[1].toUpperCase();
      });
      component = component.charAt(0).toUpperCase() + component.slice(1);

      if (this.allowedNumerableSections.includes(component)) {
        this._numerableCount++
        return this._numerableCount < 10 ? `/0${this._numerableCount}` : `/${this._numerableCount}`
      }

      return false
    }
  }
}

这个方案也能解决循环问题,但可维护性不如计算属性方案,渲染阶段的方法调用时机受Vue渲染策略影响,后续加逻辑容易出边界问题,优先用第一种方案。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 14:42:18