Skip to content

fix(vue): бесконечная реактивная петля в ResourceCategoryTree — фриз Настройки→Опции (#546) - #547

Merged
biz87 merged 1 commit into
betafrom
fix/546-tree-recursive-loop
Aug 12, 2026
Merged

fix(vue): бесконечная реактивная петля в ResourceCategoryTree — фриз Настройки→Опции (#546)#547
biz87 merged 1 commit into
betafrom
fix/546-tree-recursive-loop

Conversation

@biz87

@biz87 biz87 commented Aug 12, 2026

Copy link
Copy Markdown
Member

Fixes #546

Проблема

Админка → Настройки → «Опции» намертво виснет при открытии. В Network единственный GET /api/mgr/options висит в pending.

Причина — бесконечная реактивная петля

ResourceCategoryTree.vue: watch(() => props.modelValue, …, { deep:true }) безусловно звал ensureLockedChecked()emitSelection() эмитит update:modelValue новым массивом → родитель (OptionsGrid.selectedCategories через OptionCategoryTree v-model) обновляет модель → ссылка меняется → watch снова → бесконечно.

Петля реактивная (микротаски) — пожирает главный поток. Поэтому страница фризится, а одиночный /api/mgr/options висит pending: замороженный браузер не может обработать уже пришедший ответ.

Доказательства (воспроизведено, не гипотеза)

  • Изолированная реактивность Vue (та же схема) → Maximum recursive updates exceeded (guard Vue).
  • Сервер невиновен: OptionsController::getList в CLI — 0.006с (total=21, results=20), БД чиста (MariaDB, 21 строка).

Регрессия из 6d7a13871.

Решение (проверено на стенде)

watch(modelValue) не эхо-эмитит полученное значение — emitSelection() только когда locked-принуждение реально добавило ID, которого у родителя нет:

watch(() => props.modelValue, newIds => {
  const incoming = new Set(newIds || [])
  const next = new Set(incoming)
  lockedSet.value.forEach(id => next.add(id))
  checkedSet.value = next
  if (next.size !== incoming.size) emitSelection()
}, { deep: true })

С guard'ом на изолированном стенде — 1 эмит вместо бесконечного цикла; locked-семантика сохранена.

Радиус

Тот же ResourceCategoryTree — в ProductCategoriesTab (карточка товара → Категории) и CategoryOptionsTab. Фикс закрывает петлю для всех потребителей.

Проверка

  • ESLint ✅, npm run build ✅.
  • На DEV после hard-reload: Настройки→Опции открывается без фриза, фильтр-дерево работает; карточка товара → Категории и Категория → Опции — без зависаний (подтверждено).

watch(() => props.modelValue) called ensureLockedChecked() unconditionally,
which emits update:modelValue with a fresh array. The parent's v-model echoes it
straight back into the prop, retriggering the watch — an infinite recursive loop
(Vue: "Maximum recursive updates exceeded") that froze Settings→Options and any
page using the tree. The single /api/mgr/options request showed as pending only
because the frozen main thread never processed the (instant, 0.006s) reply.

Emit back only when locked enforcement actually adds an ID the parent lacks, so
the watch no longer echoes the value it just received. Verified in isolation:
1 emit instead of an infinite loop.
@biz87
biz87 merged commit 54eae25 into beta Aug 12, 2026
3 checks passed
@biz87
biz87 deleted the fix/546-tree-recursive-loop branch August 12, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Настройки → Опции намертво виснет: бесконечная реактивная петля в ResourceCategoryTree

1 participant