javascript

Сайд эффект реактивности и апдейта компонента во Vue 3

  • пятница, 13 октября 2023 г. в 00:00:11
https://habr.com/ru/articles/766958/

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

Насколько вы считаете подобное поведение явным или неявным? Стоит ли прибегать к таким практикам в рабочем коде? С какими подобными сайд эффектами сталкивались вы?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Считаете ли вы описанное поведение сайд эффектом?
28.57% Да 2
71.43% Нет 5
Проголосовали 7 пользователей. Воздержались 2 пользователя.