Реактивность Vue на графе объектов, содержащем циклические ссылки

Сначала немного контекста:

Я создаю приложение mineswheeper на основе 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 мне нужно либо: - завершить игру, если Box.hasBomb истинно; - показать содержимое (если в нем нет бомбы) - либо распространить вызов функции detect (), если Box.nearBombs равен 0

мой метод 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


person Doltario    schedule 28.12.2019    source источник
comment
Какова точная форма вашей структуры данных? Вы можете добавить это к своему вопросу?   -  person Michal Levý    schedule 28.12.2019


Ответы (1)


Итак, у вас есть одномерный массив объектов Box, и каждый объект содержит массив neighbors со ссылками (на основные элементы массива) на соседние с ним Box.

Для создания neighbors с configurable: false, и Vue не сможет настроить для него реактивность. Но остальные свойства объекта Box остаются реактивными.

Я установил небольшую демонстрацию я. Все важное находится в components/HelloWorld.vue компоненте.

Пример немного запутан, потому что с массивом можно легко ссылаться на «предыдущий» и «следующий» элементы с помощью индекса, но мне нужно было ввести циклические ссылки между объектами.

На моей машине это выдаст ошибку с 10000 элементами в массиве. Но если переключить this.setupRelatioships(boxes); вызов с this.setupRelatioshipsWithProperties(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
        });
      }
    },

Ранее упоминавшаяся демонстрация (кредиты на Гийом Чау)

person Michal Levý    schedule 28.12.2019
comment
Спасибо за ответ, попробовал, но вроде реагирует так же, как Object.freeze (). Я имею в виду, что представление не обновляется при вызове Box.reveal (). В приведенном вами примере я думаю, что не понимаю, как обновляются b и c при нажатии кнопки ++ - person Doltario; 28.12.2019
comment
Что касается примера ... нажатие кнопки ++ сообщает Vue о необходимости повторного рендеринга всего шаблона, и он, конечно же, будет использовать текущие значения b и c, когда это произойдет ... - person Michal Levý; 28.12.2019
comment
Хм, это странно. __neighbors - это просто ссылки на другие блоки, которые уже есть в основном массиве, верно? - person Michal Levý; 28.12.2019
comment
Хорошо, поэтому мне нужно применить логику кнопки «а» к сетке, я исследую этот вывод, потому что необходимость нажимать кнопку «обновить» каждый раз, когда пользователь нажимает на поле, может быть немного неприятным. И вы абсолютно правы в своем втором комментарии: _neighbors - это массив ссылок на существующие объекты Box - person Doltario; 28.12.2019
comment
@Doltario Вы можете взглянуть на мою собственную демонстрацию - она ​​решает проблему, и объекты остаются реактивными ... - person Michal Levý; 28.12.2019
comment
Большое спасибо за ваше время, Михал, у меня проблемы только с моей реализацией вашего решения, но оно будет работать нормально, ваша демонстрация великолепна. Задача решена ! - person Doltario; 28.12.2019