Не будь умным - делай как умеешь

С недавнего времени я уволилась с работы и нахожусь в поиске уже нашла, и с того времени прохожу собесы по Vue - самый распространённый вопрос про реактивность во Vue.

Этот вопрос может звучать по-разному: • ref vs reactive? • Что такое markRaw? • Что такого особенного есть во Vue? • Какая фишка у Vue? • В чём разница между реактивностью во Vue 2 и Vue 3?

Обычно я отвечаю, что ref - для всего, а reactive - для форм. И что ref оборачивает значение и создаёт Proxy на объект и все вложенные, а reactive создаёт Proxy напрямую на объект без обёртки. На практике же использую ref в 99.9% случаев, а про shallowRef узнала только на собесе (ой).

Предыстория И вот снова собес - у меня опять спрашивают, в чём разница между ref и reactive. Я говорю свою заученную фразу, а тут интервьюер стал копать глубже: как создаётся Proxy? Какие сайд-эффекты? И намекнул, что нужно бы знать глубже и понимать, но особо не уточнил что именно нужно знать. И после этого у меня появились сомнения - а всё ли, что я писала, я писала правильно?

Реальный кейс Список товаров с категориями, которые ставят на стоп-лист (в ресторане закончилось тесто - ставим всю категорию пиццы на стоп, чтобы гости не смогли заказать). ~2 000 позиций, приходят с бэка

Сравниваю ref, reactive, ref+markRaw, shallowRef+Set по скорости и памяти на 100, 2 000, 10 000 позиций. Про каждый подробно можно почитать в документации Vue (https://ru.vuejs.org/guide/essentials/reactivity-fundamentals).

ref Берём ref по дефолту. При записи объекта Vue рекурсивно создаёт Proxy на него и все вложенные объекты. На 10 000 элементов: инициализация 3.5 ms, замена массива 13.5 ms, выбор категории 4.8 ms.

reactive И так понятно, что он не для этого кейса, но всё же. Ну и ожидаемые результаты на 10 000: замена массива 20.7 ms - на 53% медленнее ref.

ref + markRaw ref снаружи, элементы помечены markRaw - Vue не создаёт на них Proxy. Обновление быстрее. И тут осознание: isSelected - это не свойство данных, это UI-состояние. Ему не место в объекте с API. И тут же ещё одно осознание, что у нас паттерн - мы заменяем весь массив с бэка.

shallowRef + Set Данные с API - в shallowRef (без Proxy вглубь). Выделение - отдельно в Set<string>. Замена массива - просто items.value = newData. Выбор категории - замена Set. Архитектурно чище: данные и UI-состояние разделены. На 10 000: выбор категории 0.1 ms (в 48x быстрее ref).

Круто, прирост в 48x. Но. Давайте посмотрим реальную картину с DOM. На что тратится время при клике на категорию: • Реактивность (ref/reactive - Proxy traps, trigger) - те самые миллисекунды, которые мы мерили • JS (обработчик, перебор массива • DOM (vDOM diff, patch, layout, paint) • Сеть • Анимации, сторонние скрипты

На 10 000 элементов: Без DOM: ref 4.8 ms -> shallowRef 0.1 ms (48x) С DOM: ref 65 ms -> shallowRef 41 ms (1.6x)

48x превратилось в 1.6x. Куда делись остальные? В DOM.

А что на реальном проекте? На реальном проекте мы используем vue-virtual-scroller - рендерим не 2 000 DOM-элементов, а ~20 видимых. И это даёт в 100x больше, чем любая оптимизация реактивности. Потому что лучшая оптимизация DOM - это не рендерить DOM.

Итог: хватит оптимизировать реактивность - поставьте virtual scroll и идите работать. Код бенчмарков - https://github.com/Stugi/vue-reactive

Не будь умным - делай как умеешь | Сетка — социальная сеть от hh.ru
Не будь умным - делай как умеешь | Сетка — социальная сеть от hh.ru Не будь умным - делай как умеешь | Сетка — социальная сеть от hh.ru