Для Модератора:
В первой версии статьи я действительно использовал ИИ для финального ревью и редактировании формулировок перед публикацией. Я не в полной мере осознал правила Хабра, за что прошу прощения. Я полностью с нуля переписал статью, используя только узконаправленные сервисы для проверки орфографии и пунктуации. Не знаю где именно, и нужно ли вообще было писать этот комментарий, извините за неудобства


От НЛО: Добро пожаловать на Хабр. Спасибо автору за принятие правил и проделанную работу. НЛО одобряет такой подход к делу и оставляет это здесь «Потомству в пример» Высказывание приписывают императору Николаю I

Я работаю фронтенд-разработчиком больше пяти лет, из них четыре года пишу код и делаю ревью на Vue-проектах. И в каждой компании я рано или поздно сталкивался с таким кодом на ревью:

watch(props, (newProps) => {
  fetchData(newProps.userId)
})

Напрямую в документации Vue нигде не сказано, что это антипаттерн. Но на практике такой подход почти никогда не является лучшим решением, и его стоит избегать. Давайте разберёмся почему.


props в Vue 3 — реактивный Proxy с отслеживанием только на верхнем уровне свойств. Когда мы передаём его первым аргументом в watch, Vue обходит свойства верхнего уровня и собирает зависимости со всех из них. В результате коллбэк будет вызываться при изменении любого пропса компонента.

Очевидно, что deep: false тут не поможет (хотя я встречал и такой вариант). Опция deep управляет рекурсивным обходом вложенных объектов, но зависимости верхнего уровня всё равно будут собраны независимо от значения deep.

В чём же проблема? В том, что почти никогда не нужно следить за всем объектом пропсов целиком. Мы либо получим вызов функции в коллбэке тогда, когда нам это не нужно по бизнес-логике, либо будем писать проверки вроде oldVal.userId !== newVal.userId, что избыточно и создаёт лишнюю нагрузку на того, кто читает этот код.


Самый запущенный вариант этого паттерна, который я видел, выглядел так:

watch(props, (newProps, oldProps) => {
  if (newProps.userId !== oldProps.userId) {
    fetchData(newProps.userId)
    resetForm()
  }
  if (newProps.theme !== oldProps.theme) {
    updateTheme(newProps.theme)
  }
  if (newProps.error !== oldProps.error) {
    showError(newProps.error)
  }
})

Три независимых сценария в одном вотчере, в каждом ручное сравнение old и new. Такой вотчер объединяет несколько независимых реакций на изменения пропсов, что усложняет поддержку. А через какое-то время, когда кто-то дописывает четвёртый сценарий, читать это уже физически неприятно.

Вместо этого лучше написать три отдельных watch:

watch(() => props.userId, () => {
  fetchData(props.userId)
  resetForm()
})

watch(() => props.theme, () => {
  updateTheme(props.theme)
})

watch(() => props.error, () => {
  showError(props.error)
})

Каждый вотчер делает одну вещь и зависит ровно от одного пропса. Когда первым аргументом идёт геттер-функция, Vue запускает её в реактивном контексте и регистрирует только те зависимости, которые были затронуты при выполнении. В этом случае зависимостью становится только props.userId.


Вернёмся к тому ревью, с которого началась эта история. Раз это зачастую неудачный паттерн, логично предположить, что существует правило для ESLint, которое контролирует использование всего объекта props в качестве триггера watch. Я даже удивился, почему до сих пор не добавил его в наш конфигурационный файл. Спустя полчаса поисков — самостоятельных и с помощью ИИ — я с удивлением и разочарованием обнаружил, что ни один индексируемый плагин такого правила не предоставляет.

До конца рабочего дня оставалось ещё целых два часа, а под рукой был личный ноутбук с Cursor. Я открыл документацию ESLint по написанию собственных плагинов, набросал небольшой план MVP, написал пару промптов... и через час уже тестировал подключённую к рабочему проекту версию 1.0.0 моей npm-библиотеки с "vue-arch/no-watch-entire-props": "error".

// eslint.config.js
export default [
  {
    plugins: { 'vue-arch': vueArchPlugin },
    rules: {
      'vue-arch/no-watch-entire-props': 'error'
    }
  }
]

Правило работает: watch(props, ...) подсвечивается в редакторе приятным глазу красным цветом ещё до ревью.
Репозиторий с правилом здесь: https://github.com/White11010/eslint-plugin-vue-arch