【问题标题】:Vue reactivity on object graph containing cyclic references包含循环引用的对象图上的 Vue 反应性
【发布时间】:2020-04-17 22:36:46
【问题描述】:

首先是一些上下文:

我正在构建一个基于 VueJS 的扫雷应用程序。 我的网格是一个对象树:每个 Box 对象都有一个“邻居”属性,其中包含 Box 对象,这些对象是紧随其后的盒子。

结构是圆形的,但很好。

现在的问题:

首先我尝试在一个小网格 (5x5) 上运行良好,但是在尝试生成更大的网格 (50x50) 时,Vue 在设置观察者时产生了“超出最大调用堆栈大小”错误,这是日志

对于 vue 来说,树似乎太大而无法处理反应性。

已在冻结我的对象时得到确认,并且效果很好(没有调用堆栈错误):

data() {
    const game = new MinesWheeper(30, 30, 40)
    Object.freeze(game)
    return {
      game
    }
  }

但是由于冻结,反应性(显然)下降了。不过,我需要反应性才能在点击时显示框。

现在这是我发现的问题/线索:

  • 你有没有遇到过 Vue 的调用堆栈问题,或者我错过了什么?

  • 有没有办法让反应性在具有这种对象结构的 VueJS 上工作? (VueX 是解决方案的一部分吗?我真的不知道)

  • 或者我应该考虑使用 VueJS 以外的东西吗? (用香草做吗??)

提前致谢, 抱歉,如果我的帖子很乱,这是我 5 年来的第一篇文章,我很紧张,哈哈

编辑:这是游戏对象的样子:

game: { // MinesWheeper
  _grid: { // Grid
    "_bombsNumber": 40
    _boxes: [ // Array of Box
      {
        "_hasBomb": true
        "_index": 0
        "_isRevealed": false
        "_neighbors": [Box, Box, Box, Box]
        "hasBomb": true
        "index": 0
        "isRevealed": false
        "nearBombs": 1
      }
    ]
  }
}

此外,在单击框时,我需要: - 如果 Box.hasBomb 为真,则结束游戏 - 显示内容(如果里面没有炸弹) - 如果 Box.nearBombs 等于 0,则传播reveal()调用

我的 Box.reveal() 方法是递归的:

reveal() {
    if (this._isRevealed) return

    this._isRevealed = true

    if (this.hasBomb) {
      console.log('Game over')
    } else if (this.nearBombs === 0) {
      this._neighbors.forEach(neighbor => {
        neighbor.reveal()
      })
    }
  }

这就是为什么我认为我需要响应式来更新每个 Box.reveal 调用的视图

【问题讨论】:

  • 你的数据结构的确切形状是什么?您可以将其添加到您的问题中吗?

标签: vue.js vuejs2 tree vue-reactivity


【解决方案1】:

所以你有一个 Box 对象的一维数组,每个对象都包含 neighbors 数组,其中包含对其相邻 Boxes 的引用(对主数组项)。

您可以使用Object.defineProperty()configurable: false 创建neighbors 属性,而Vue 将无法在其上设置响应性。但 Box 对象的其余属性仍然是响应式的。

我自己设置了一个little demo。所有重要的都在components/HelloWorld.vue 组件中。

示例并不复杂,因为数组可以通过索引轻松引用“prev”和“next”元素,但我需要在对象之间引入循环引用。

在我的机器上,它会在数组中有 10000 个项目时引发错误。但是如果你用this.setupRelatioshipsWithProperties(boxes); 切换this.setupRelatioships(boxes); 调用,错误就会消失。

setupRelatioships(boxes) {
      for (let i = 0; i < boxes.length; i++) {
        boxes[i].prev = i > 0 ? boxes[i - 1] : null;
        boxes[i].next = i < boxes.length - 1 ? boxes[i + 1] : null;
      }
    },
    setupRelatioshipsWithProperties(boxes) {
      const opts = {
        configurable: false,
        enumerable: true,
        writable: true
      };

      for (let i = 0; i < boxes.length; i++) {
        Object.defineProperty(boxes[i], "prev", {
          ...opts,
          value: i > 0 ? boxes[i - 1] : null
        });
        Object.defineProperty(boxes[i], "next", {
          ...opts,
          value: i < boxes.length - 1 ? boxes[i + 1] : null
        });
      }
    },

以前引用过Demo(归功于Guillaume Chau

【讨论】:

  • 感谢回答,我试过了,但是好像和Object.freeze()的反应一样。我的意思是在 Box.reveal() 调用上没有更新视图。在您附加的示例中,我认为单击 a++ 按钮时我不知道 b 和 c 如何刷新
  • 关于示例...单击 a++ 按钮会告诉 Vue 重新渲染整个模板,它当然会使用 bc 的当前值发生这种情况......跨度>
  • 嗯,这很奇怪。 __neighbors 只是对已经在主数组中的其他框的引用,对吧?
  • 哦,好的,所以我需要将“a”按钮的逻辑应用于网格,我将探讨这个线索,因为每次用户单击框时都必须单击“刷新”按钮可能有点不愉快。您的第二条评论绝对正确:_neighbors 是对现有 Box 对象的引用数组
  • @Doltario 你可以看看我自己的演示 - 它解决了问题并且对象保持反应...
猜你喜欢
  • 1970-01-01
  • 2020-10-23
  • 1970-01-01
  • 2010-12-07
  • 2018-11-29
  • 2012-03-12
  • 1970-01-01
相关资源
最近更新 更多