Включить react-hooks/exhaustive-deps и разобрать устаревшие замыкания

Правило было не задано вовсе: плагин eslint-plugin-react-hooks подключает
профиль @microsoft/eslint-config-spfx/lib/profiles/react, но правил из него
не включает. Теперь в .eslintrc.js явно заданы rules-of-hooks (error) и
exhaustive-deps (warn); сборка проходит с нулём предупреждений.

Что реально чинилось, а не затыкалось:

- shared/useStableCallback.ts — обёртка с неизменной ссылкой над колбэком-пропом
  (аналог будущего useEffectEvent). Ссылка стабильна, внутри всегда актуальная
  версия колбэка, поэтому её можно честно класть в зависимости.
- onCodeLoaded во всех forms/*/*View.tsx и *Form.tsx идёт в эффект загрузки
  через эту обёртку: просто дописать проп в зависимости нельзя — заявка
  перезапрашивалась бы на каждый рендер родителя.
- useRequestSubmit: run стабилизирован (useCallback + savingRef вместо чтения
  состояния), возвращаемый объект мемоизирован. Формы берут из него стабильный
  setError и указывают в зависимостях его, а не submit целиком.
- shared/usePersistedQuery.ts (useRoutedQuery) — было устаревшее замыкание:
  эффект навсегда запоминал первую версию onQueryChange, а та замкнута на route
  на момент монтирования, то есть набранный запрос мог уехать в маршрут чужого
  раздела. Теперь зовётся актуальная версия.
- propertyPane/DebugUserField.tsx — то же самое с onResolved.
- Формы: в эффект "новая заявка по умолчанию на себя" дописан context
  (единственный экземпляр на жизнь веб-части, лишних перезапусков не даёт),
  в ReferralForm ещё и мемоизированный approval.
- RequestsTableView: dataKey в зависимости (он и так выводится из view/
  statusFilter/debouncedQuery, которые там уже есть).

Осознанные отступления оставлены с объяснением ПОЧЕМУ: useMatrixEditor (момент
перечитывания матрицы задаёт вызывающий через reloadKey), companiesFieldValue
в RecruitingHire/RecruitingSelection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-09-01 17:11:29 +05:00
parent ddce6b4f25
commit c5a443b5d1
27 changed files with 322 additions and 93 deletions
+16
View File
@@ -290,6 +290,22 @@ module.exports = {
// Rationale to disable: !!{}
'no-extra-boolean-cast': 0,
// ====================================================================
// eslint-plugin-react-hooks (плагин подключён профилем
// @microsoft/eslint-config-spfx/lib/profiles/react, но правил он не задаёт)
// ====================================================================
// RATIONALE: Хуки нельзя звать условно/в циклах — иначе React
// перепутает состояние между рендерами.
'react-hooks/rules-of-hooks': 2,
// RATIONALE: Неполный массив зависимостей useEffect/useCallback/
// useMemo — это устаревшее замыкание: эффект продолжает
// видеть старые пропсы и state. Держим предупреждением:
// там, где отступление осознанное (например, эффект
// должен сработать только на смену id, а не на каждое
// изменение колбэка), рядом стоит
// // eslint-disable-next-line react-hooks/exhaustive-deps
// с объяснением причины.
'react-hooks/exhaustive-deps': 1,
// ====================================================================
// @microsoft/eslint-plugin-spfx
// ====================================================================
'@microsoft/spfx/no-require-ensure': 2,