Сайд эффект реактивности и апдейта компонента во Vue 3
- пятница, 13 октября 2023 г. в 00:00:11
Хочу рассказать о небольшом кейсе, связанном с работой реактивности во Vue 3. Кейс касается взаимосвязи ref/reactive, v-for/v-if, :class, функций и того, что у нас находится в <template>. Сразу оговорюсь, что под капотом не смотрел, поэтому детальных объяснений не ждите. Наоборот, хотелось бы услышать ваши мнения, сталкивались ли вы с подобными сайд эффектами.
Представим что у нас есть реактивная переменная, внутри которой находится пустой массив.
const arr = ref([]);В этот массив по клику на кнопку добавляется один рандомный элемент.
const add = () => {
const random = Math.floor(Math.random() * 10);
arr.value.push(random);
}
<button @click="add">Add</button>Если мы используем arr напрямую в разметке внутри тега <template>, то нашим ожидаемым поведением является изменение в разметке и вызов апдейта компонента на каждый клик по кнопке. А если мы не используем arr в том или ином виде в разметке, то, соответственно, апдейта компонента не происходит, хотя сами данные, хранящиеся в arr изменяются.
// Вызов onUpdated при каждом добавление нового элемента.
onBeforeUpdate(() => {
console.log('before update');
})
onUpdated(() => {
console.log('updated');
})
<span>{{ arr }}</span>Логично, что мы можем распространить наши знания на v-if. Так, если мы используем его в разметке и проверяем результат вызова функции на true/false, то вызов самой функции происходит один раз при добавлении компонента в DOM.
const fn = () => {
console.log('fn');
console.log(arr.value);
return true;
}
<span v-if="fn()">fn</span>Однако, если немного видоизменить функцию add, которая будет присваивать в arr пустой массив, то fn будет вызываться при каждом клике на кнопку вместе с апдейтом компонента. При этом такое не работает с примитивами, в этом случае апдейт будет только один раз, если значение переменной не изменяется на втором и последующих кликах.
// Вызов onUpdated при каждом добавление нового элемента.
const add = () => arr.value = [];
// Вызов onUpdated только при первом вызове функции add.
const add = () => arr.value = 1;В случае с v-if важно подчеркнуть, что вызов функции fn при апдейте будет ровно столько раз, сколько <span v-if="fn()">fn</span> у нас есть на странице.
С каким сайд эффектом описанной выше логики столкнулся на одном из рабочих проектов лично я. Представим, что у нас есть список ul, каждый элемент li с директивой :class. При клике на li добавляется элемент в массив arr. А функция check, соответственно возвращает true/false при проверке наличия элемента в массиве. Если check вернул true, то к соответствующему li добавляется класс .active, который меняет цвет на red.
const check = (idx) => {
console.log('check');
const found = arr.value.find((item) => item === idx);
if(found) return true;
else return false;
}
const add = (el) => arr.value.push(el);
<ul>
<li :class="{'active': check(1)}" @click="add(1)">1</li>
<li :class="{'active': check(2)}" @click="add(2)">2</li>
<li :class="{'active': check(3)}" @click="add(3)">3</li>
</ul>
// Косоль при клике
[Log] before update
[Log] check
[Log] check
[Log] check
[Log] updated
Первый раз функция check закономерно вызывается три раза при маунте компонента. Поскольку arr пустой, то и класса .active нет ни у одного элемента li. Но при этом мы получаем изменение класса у элемента li при клике на него. Происходит это потому, что при клике меняется значение arr, затем срабатывает апдейт компонента. После onBeforeUpdate отрабатывает функция check равно столько раз, сколько у нас элементов li. В результате проверки чек у нас добавляется класс там, где check вернул true.
В итоге задача, поставленная перед разработчиком была выполнена. Тестировщик проверил и отметил, что все работает корректно - класс меняется при клике на элемент. Однако, на мой взгляд, такое решение не оптимальное. И даже не столько из-за вызова функций на каждом элементе li . По сути, мы сталкиваемся с сайд эффектом реактивности и жизненного цикла компонента, изменения в которых зависят от самого движка Vue. Логично, что сделав arr не реактивным, мы лишаемся апдейта и изменения класса по клику.
Насколько вы считаете подобное поведение явным или неявным? Стоит ли прибегать к таким практикам в рабочем коде? С какими подобными сайд эффектами сталкивались вы?