Перейти к содержимому
Star

Health ​

Вкладка Health — линтер времени выполнения для проблем, которые постоянно всплывают в issues effector. Каждый пункт говорит, что случилось, где (ссылка на файл открывает панель Sources на нужной строке) и как это исправить.

Вкладка Health: вызов вне scope, $errors обновлён больше одного раза, sample остановлен filter и «Зацикливаний нет»Вкладка Health: вызов вне scope, $errors обновлён больше одного раза, sample остановлен filter и «Зацикливаний нет»

Как читать список ​

Пункты отсортированы по уровню: ошибки (красный !), предупреждения (жёлтый !), информация (i) и пройденные проверки (✓). На ярлыке вкладки — число ошибок, а если их нет — предупреждений.

  • ×3 · последний #8 — сколько раз это случилось и последняя транзакция;
  • клик по пункту открывает транзакцию в логе (или стор в Stores);
  • Показать в Graph фокусируется на причастных юнитах (циклы, устаревшее чтение, двойные обновления, запись в производные сторы, аварийная остановка);
  • Проверить производные сторы (вверху вкладки) запускает проверку согласованности по запросу.

Пункты, которые происходят из транзакций, очищаются вместе с логом; пункты о юнитах и scope остаются.

Health в демо-лаборатории: аварийная остановка цикла, исключение в $divisor.map, вызов вне scope ×3, устаревшее чтение, «Производные сторы совпадают с источниками»

Проверки по транзакциям ​

Вызов вне scope ​

Ошибка · Вызов вне scope: greetingShown

Юнит вызван вне scope, хотя приложение использует fork(). Изменение ушло в глобальный граф, а приложение читает свой scope и его не видит. Обычно это вызов из setTimeout, сокета, стороннего колбэка или прямой вызов вместо useUnit. Строка в логе красная, с бейджем вне scope.

Исправление: привяжите вызов до выхода из эффекта — const fn = scopeBind(greetingShown) — или вызывайте через useUnit.

Обновлён больше одного раза за транзакцию ​

Предупреждение · $errors обновлён больше одного раза за транзакцию

Стор пересчитался дважды за один проход. effector упорядочивает шаги combine и sample по порядку объявления, а не по графу, поэтому потребитель, объявленный раньше источников, сначала читает старое значение. В логе — бейдж 2× $errors.

Исправление: объявите стор после юнитов, которые его питают, или соберите его через combine вместо цепочки sample.

Остановлен filter ​

Информация · sample(…) остановлен filter

filter вернул false, target не вызван. Само по себе не ошибка — но когда кнопка «ничего не делает», причина обычно здесь. Откройте транзакцию, чтобы увидеть значения source и filter.

Исключение в чистой функции ​

Ошибка · Исключение в $divisor.map → $divisor → *

Бросил редьюсер, map, combine или filter. effector пишет ошибку в консоль и останавливает только эту ветку: соседние ветки уже применились, поэтому производные сторы могут разойтись с источниками. В подробностях перечислено, что не выполнилось из-за исключения.

Исправление: редьюсеры и функции маппинга должны быть чистыми и не бросать исключений; рискованный код — в эффект.

Прочитал до обновления (устаревшее чтение) ​

Предупреждение · sample($price, $qty → totalLogged) прочитал $qty до обновления

Устаревшее чтение в логе: priceChanged с бейджем stale read и объяснением во вкладке «Почему»

В той же транзакции стор изменился после того, как sample взял значение, поэтому sample использовал старое. effector упорядочивает sample по объявлению и приоритету, а не по потоку данных.

Исправление: запускайте sample от этого стора (clock: $qty) или объявите его после юнитов, которые обновляют стор.

Запись в производный стор ​

Предупреждение · В производный стор $x пишут напрямую

Стор из map/combine получил значение от sample или события. В effector это устарело: следующее обновление источников перезапишет значение, а до этого стор и источники расходятся.

Исправление: сделайте его обычным стором (createStore + .on / sample) или пишите в его источники.

Эффект завершился ошибкой ​

Информация · signupFx завершился ошибкой

Эффект завершился с ошибкой; в подробностях — её сообщение. Помогает заметить сбои, которые UI молча проглатывает.

Возможный бесконечный цикл ​

Ошибка · Возможный бесконечный цикл от loopStart

Одна транзакция выполнила больше maxSteps шагов (по умолчанию 5000), и её дерево шагов обрезано. Синхронный цикл через события не останавливается и вешает вкладку; цикл через сторы останавливается, когда значение перестаёт меняться.

Исправление: откройте Graph и найдите цикл, который проходит только через события.

deffector остановил бесконечный цикл ​

Ошибка · deffector остановил бесконечный цикл

Один синхронный проход ядра превысил hardStop шагов (по умолчанию 100 000). deffector оборвал распространение, чтобы вкладка не зависла: каждый узел, выполненный сверх лимита, перестаёт передавать значения дальше, ядро опустошает очередь, а на следующей микрозадаче связи возвращаются. Состояние может быть обновлено частично; транзакция помечена остановлено. В подробностях — юниты, которые выполнялись чаще всего.

Исправление: найдите цикл в Graph (он проходит через эти юниты) и разорвите его filter-ом или вынесите вызов из watcher. См. Производительность.

Зацикливаний нет ​

Пройдено · Зацикливаний нет — ни одна транзакция не выполнила больше maxSteps шагов.

Проверки юнитов и scope ​

Сторы без sid ​

Предупреждение · У 3 из 20 сторов нет sid

Без sid serialize(scope) молча пропускает стор, гидрация до него не доходит, а записанный трейс сопоставит его только по имени. Перемещение во времени в пределах страницы работает.

Исправление: подключите effector/babel-plugin или @effector/swc-plugin для этих файлов.

Повторяющийся sid ​

Ошибка · Повторяющийся sid 2r0u4d|xz7n29

У нескольких юнитов один sid: serialize, гидрация и подмена хендлеров их перепутают. Обычно фабрика не указана в factories плагина или плагин применён дважды. (Юниты, созданные заново после старта, показываются как утечка.)

Исправление: добавьте фабрику в опцию factories плагина.

Глобальный режим ​

Предупреждение · Глобальный режим: нет fork()

Приложение работает с глобальным графом. Перемещение во времени записывает значения на месте через launch: watchers, sample и эффекты на это реагируют, поэтому результат не гарантирован. Воспроизведение подменяет хендлеры эффектов глобально.

Исправление: лучше использовать fork() и рендерить приложение внутри <DeffectorProvider value={scope}>.

Scope без адаптера ​

Предупреждение · Используется scope, но приложение не обёрнуто в DeffectorProvider

Без адаптера deffector не может показать scope из прошлого, поэтому переход записывает значения в живой scope на месте.

Исправление: замените Provider из effector на DeffectorProvider из @deffector/agent/react (или /vue, /solid).

Значения с сервера без стора ​

Предупреждение · 2 значени(й) с сервера не нашли стор на клиенте

fork({ values }) или гидрация получили sid, которых нет на клиенте. Либо сервер и клиент собирают модель по-разному (опции плагина, factories), либо модуль ещё не загружен. Попробуйте в демо-лаборатории с ?hydrate.

Исправление: используйте одинаковую конфигурацию babel/swc-плагина на сервере и клиенте; перечислите фабрики в опциях плагина.

Создан N раз ​

Предупреждение · $field создан 12 раз

Юниты из одного и того же места в коде снова и снова создаются после старта приложения. Юниты, созданные в компоненте, watcher-е или эффекте, не собираются сборщиком мусора и ломают SSR (каждый раз новые sid). Сообщается, когда одно место создало юниты хотя бы трижды.

Исправление: создавайте модель один раз на уровне модуля или держите экземпляр фабрики вне рендера (useMemo не спасает в StrictMode).

Юниты, созданные после старта ​

Информация · 14 юнит(ов) создано после старта

Перечисляет файлы, которые создавали юниты после старта приложения. Лениво загруженные чанки делают это один раз, это нормально; повторное создание показывается отдельно, как выше.

Цикл только через события ​

Предупреждение · Цикл только через события: loopStart → loopNext → …

Найден в статическом графе: события вызывают друг друга без стора между ними. Если filter его не остановит, один вызов зациклится и повесит вкладку.

Исправление: добавьте filter или проведите цикл через стор (стор останавливается, когда значение перестаёт меняться).

Цикл через стор ​

Информация · Цикл через стор: clamp → $clamped → …

Заканчивается, когда значение стора перестаёт меняться. Для сходящейся логики это нормально; если значение каждый раз новое (счётчик, новый объект), цикл идёт до лимита шагов.

История до открытия панели ​

Информация · История до открытия панели не записана

Агент стоит на паузе, пока DevTools закрыты: первые 10 с после загрузки (graceMs) он пишет, поэтому DevTools можно открыть и перезагрузить страницу — начало сохранится.

Исправление: откройте DevTools и перезагрузите страницу, чтобы записать с самого начала, или задайте attachDeffector({ whileClosed: 'record' }).

Другой инструмент перехватил инспектор ​

Ошибка · Другой инструмент перехватил инспектор effector

В effector один слот инспектора. effector/inspect (его используют effector-logger и @effector/redux-devtools-adapter) загрузился после deffector и заменил его: новые транзакции не записываются. Пока панель открыта, deffector проверяет слот каждые несколько секунд.

Исправление: уберите другой инструмент в разработке или импортируйте его до @deffector/agent. Кнопка Вернуть у пункта забирает слот обратно.

Производные сторы разошлись с источниками ​

Проверить производные сторы пересчитывает каждый производный стор из текущих значений записываемых сторов во временном fork() и сравнивает результат с тем, что хранит приложение.

  • Пройдено · Производные сторы совпадают с источниками;
  • Предупреждение · 2 производн(ых) стор(ов) разошлись с источниками — обычно после исключения в map/combine или прямой записи в производный стор. Исправление: посмотрите последнюю транзакцию с ошибкой и исправьте функцию, которая бросает исключение.

Проверка запускается по запросу, потому что ещё раз выполняет все производные вычисления. Она есть и в палитре команд.