Props, watch, два часа до конца рабочего дня

от автора

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

От НЛО: Добро пожаловать на Хабр. Спасибо автору за принятие правил и проделанную работу. НЛО одобряет такой подход к делу и оставляет это здесь «Потомству в пример» Высказывание приписывают императору Николаю 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.jsexport default [  {    plugins: { 'vue-arch': vueArchPlugin },    rules: {      'vue-arch/no-watch-entire-props': 'error'    }  }]

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

ссылка на оригинал статьи https://habr.com/ru/articles/1045941/