Vue 对包含循环引用的对象图的反应性
Vue reactivity on object graph containing cyclic references
首先是一些上下文:
我正在构建一个基于 VueJS 的扫雷应用程序。
我的网格是一个对象树:每个 Box 对象都有一个 'neighbors' 属性,其中包含 Box 对象,这些对象就是下一个框。
结构是圆形的,但是很好。
现在的问题:
首先我尝试了一个小网格 (5x5) 它工作正常,但是当尝试生成一个更大的网格 (50x50) 时,Vue 在设置观察者时产生了一个 'Maximum call stack size exceeded' 错误,这是日志
对于 vue 来说,这棵树似乎太大了,无法处理反应性。
冻结我的对象时已经确认,它工作正常(没有调用堆栈错误):
data() {
const game = new MinesWheeper(30, 30, 40)
Object.freeze(game)
return {
game
}
}
但是由于冻结,反应性(显然)下降了。不过,我需要反应性才能在点击时显示框。
现在这是我的 questions/leads 我发现:
你有没有遇到过 Vue 的这个调用堆栈问题,或者我错过了什么?
有没有办法让反应性在具有这种对象结构的 VueJS 上工作? (VueX 是解决方案的一部分吗?我真的不知道)
或者我应该考虑使用 VueJS 以外的东西吗? (用香草做吗??)
提前致谢,
对不起,如果我的 post 很乱,这是我 5 年来第一次 post,我很紧张 lol
编辑:这是游戏对象的样子:
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 调用时更新视图
所以你有一个 Box 对象的一维数组,每个对象包含 neighbors
数组,其中引用(到主数组项)到它的相邻 Box。
您可以使用 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 (credits to Guillaume Chau)
首先是一些上下文:
我正在构建一个基于 VueJS 的扫雷应用程序。 我的网格是一个对象树:每个 Box 对象都有一个 'neighbors' 属性,其中包含 Box 对象,这些对象就是下一个框。
结构是圆形的,但是很好。
现在的问题:
首先我尝试了一个小网格 (5x5) 它工作正常,但是当尝试生成一个更大的网格 (50x50) 时,Vue 在设置观察者时产生了一个 'Maximum call stack size exceeded' 错误,这是日志
对于 vue 来说,这棵树似乎太大了,无法处理反应性。
冻结我的对象时已经确认,它工作正常(没有调用堆栈错误):
data() {
const game = new MinesWheeper(30, 30, 40)
Object.freeze(game)
return {
game
}
}
但是由于冻结,反应性(显然)下降了。不过,我需要反应性才能在点击时显示框。
现在这是我的 questions/leads 我发现:
你有没有遇到过 Vue 的这个调用堆栈问题,或者我错过了什么?
有没有办法让反应性在具有这种对象结构的 VueJS 上工作? (VueX 是解决方案的一部分吗?我真的不知道)
或者我应该考虑使用 VueJS 以外的东西吗? (用香草做吗??)
提前致谢, 对不起,如果我的 post 很乱,这是我 5 年来第一次 post,我很紧张 lol
编辑:这是游戏对象的样子:
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 调用时更新视图
所以你有一个 Box 对象的一维数组,每个对象包含 neighbors
数组,其中引用(到主数组项)到它的相邻 Box。
您可以使用 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 (credits to Guillaume Chau)