В чем проблема watch с опцией deep во Vue и что он отслеживает
Что проверяют
Понимаете ли вы, как реактивность Vue 3 отслеживает зависимости и во что обходится глубокое наблюдение. Следом обычно спрашивают, почему в колбэке deep-наблюдателя новое и старое значения совпадают и чем такой watch заменить.
Как отвечать
Без deep наблюдатель за геттером или ref реагирует только на замену значения целиком. watch(() => form.user, cb) не сработает при изменении form.user.name, а watch на ref с массивом не заметит push, только присваивание нового массива.
С deep: true Vue при каждом запуске наблюдателя рекурсивно обходит всю структуру и читает каждое вложенное свойство, чтобы на него подписаться. Отсюда проблемы:
- стоимость: обход идёт по всему объекту, включая то, что вам не нужно. На списке из тысяч записей изменение одного поля означает новый полный обход;
- одинаковые
newValиoldVal: при мутации это ссылка на один и тот же объект, сравнить «было и стало» нельзя. Нужна копия или наблюдение за конкретным полем; - лишние срабатывания: колбэк вызывается на любое вложенное изменение, в том числе на служебные поля, и автосохранение или запрос уходит чаще, чем нужно.
Учтите неявный случай: если передать в watch сам reactive-объект, наблюдение уже глубокое, даже без опции.
Альтернативы, которые стоит назвать: геттер на конкретное поле () => form.user.name или массив таких источников; computed, если нужно производное значение; watchEffect, который подписывается только на реально прочитанные свойства; shallowRef с заменой объекта целиком для больших данных. С Vue 3.5 deep принимает число, глубину обхода, например deep: 1.
Где спотыкаются
Ставят deep: true по привычке, «чтобы точно сработало». Работает, но незаметно добавляет полный обход структуры на каждое изменение.
Сравнивают newVal и oldVal в глубоком наблюдателе и удивляются, что они всегда равны. Если нужно сравнение, наблюдайте за геттером, который возвращает конкретные поля или их копию.
Не знают, что reactive-объект, переданный в watch напрямую, отслеживается глубоко сам по себе. В итоге получают те же расходы, что и с deep: true, ни разу не написав эту опцию.