Сайд эффект реактивности и апдейта компонента во 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
не реактивным, мы лишаемся апдейта и изменения класса по клику.
Насколько вы считаете подобное поведение явным или неявным? Стоит ли прибегать к таким практикам в рабочем коде? С какими подобными сайд эффектами сталкивались вы?