Вопрос или проблема
Как я могу предотвратить взаимные обновления отслеживаемых элементов в Vuejs?
const width = ref(0);
const height = ref(0);
watch(width, async (newItem, oldItem) => {
console.log(`width: ${oldItem}->${newItem}`);
height.value = newItem / 2;
});
watch(height, async (newItem, oldItem) => {
console.log(`height: ${oldItem}->${newItem}`);
width.value = newItem * 2;
});
В результате я вижу такой вывод в консоли
width: 1->2
height: 0.5->1
width: 2->2
Полный код vuejs:
<script setup>
import { ref } from 'vue'
import { watch } from 'vue'
const width = ref(0);
const height = ref(0);
watch(width, async (newItem, oldItem) => {
console.log(`width: ${oldItem}->${newItem}`);
height.value = newItem / 2;
});
watch(height, async (newItem, oldItem) => {
console.log(`height: ${oldItem}->${newItem}`);
width.value = newItem * 2;
});
</script>
<template>
<div class="item">
<div class="details">
<h3>
Ширина {{ width }}
</h3>
<input v-model="width" placeholder="0" />
</div>
</div>
<div class="item">
<div class="details">
<h3>
Высота {{ height }}
</h3>
<input v-model="height" placeholder="0" />
</div>
</div>
</template>
Если вы выведете значения с помощью JSON.stringify
, вы заметите, что тип изменился с строки на число из-за вычислений:
Песочница
width: 0->"2"
height: 0->1
width: "2"->2
Просто используйте v-model.number="width"
, чтобы работать только с числами: Песочница
Чтобы избежать ввода нечисловых значений, я бы предложил использовать событие keydown
:
const preventAlpha = e => {
if(/[^\d.]/.test(e.key) && e.key.length === 1){
e.preventDefault();
} else
if(e.key === '.' && e.target.value.includes('.')){
e.preventDefault();
} else
if(e.target.value === '' && e.key === '.'){
e.target.value="0";
}
}
const vNumber = {
mounted: el => el.addEventListener('keydown', preventAlpha),
unmounted: el => el.removeEventListener('keydown', preventAlpha)
}
Ответ или решение
Предотвращение взаимных обновлений отслеживаемых элементов в Vue.js
Введение
В современных веб-приложениях часто возникает необходимость синхронизации данных между различными компонентами. Однако, в некоторых случаях, такая синхронизация может привести к бесконечным циклам обновлений, когда изменения одного элемента вызывают обновление другого, тем самым возвращая нас к первоначальному состоянию. Рассмотрим, как избежать данной проблемы на примере отслеживаемых элементов в Vue.js.
Проблема
В приведенном коде Vue.js у нас есть два отслеживаемых элемента — width
(ширина) и height
(высота). Каждый раз, когда одно из значений обновляется, оно меняет другое значение. При этом, когда одно из значений меняется, оно вызывает обновление другого, что приводит к бесконечному циклу обновлений. Результаты, которые мы видим в консоли, подтверждают эту проблему:
width: 1->2
height: 0.5->1
width: 2->2
Решение
Чтобы предотвратить взаимные обновления между width
и height
, нам необходимо реализовать условие, которое позволит избежать обновления, если новое значение является производным от уже присутствующего значения.
Подход с флагами
Одним из самых простых способов остановить циклы обновления является использование флагов, чтобы временно отключить обновление одного из значений в процессе обновления другого. Рассмотрим, как это можно сделать:
<script setup>
import { ref, watch, nextTick } from 'vue';
const width = ref(0);
const height = ref(0);
let updatingWidth = false;
let updatingHeight = false;
watch(width, async (newItem, oldItem) => {
if (updatingHeight) return; // если обновляем высоту, выходим
console.log(`width: ${oldItem}->${newItem}`);
updatingHeight = true; // установка флага
height.value = newItem / 2;
await nextTick();
updatingHeight = false; // сброс флага
});
watch(height, async (newItem, oldItem) => {
if (updatingWidth) return; // если обновляем ширину, выходим
console.log(`height: ${oldItem}->${newItem}`);
updatingWidth = true; // установка флага
width.value = newItem * 2;
await nextTick();
updatingWidth = false; // сброс флага
});
</script>
В этом коде мы используем два булевых флага updatingWidth
и updatingHeight
. Эти флаги позволяют нам контролировать, происходит ли обновление одного из элементов в данный момент времени. Если флаг установлен, мы просто выходим из функции обновления.
Использование v-model.number
Другой проблемой, с которой мы сталкиваемся, является некорректное преобразование типов при вводе значений. Чтобы гарантировать, что значения оставались числовыми, можно использовать модификатор v-model.number
в шаблоне:
<input v-model.number="width" placeholder="0" />
<input v-model.number="height" placeholder="0" />
Таким образом, все значения будут всегда числовыми, что также предотвратит потенциальные ошибки, связанные с типами данных.
Ограничение ввода только числовых значений
Для того чтобы дополнительно обеспечить корректность вводимых данных, мы можем использовать обработчик события keydown
, который будет предотвращать ввод некорректных символов.
const preventAlpha = e => {
if(/[^\d.]/.test(e.key) && e.key.length === 1) {
e.preventDefault();
} else if (e.key === '.' && e.target.value.includes('.')) {
e.preventDefault();
} else if (e.target.value === '' && e.key === '.') {
e.target.value = "0";
}
}
const vNumber = {
mounted: el => el.addEventListener('keydown', preventAlpha),
unmounted: el => el.removeEventListener('keydown', preventAlpha)
}
Добавляя этот обработчик на элементы ввода, мы можем избежать ввода недопустимых символов.
Заключение
Предотвращение взаимных обновлений между отслеживаемыми элементами в Vue.js является важной задачей для избежания бесконечных циклов обновлений. Использование флагов для контроля процесса обновления, применение v-model.number
и введение ограничений на тип вводимых значений являются эффективными методами, которые помогут решить эту проблему. При грамотном управлении данными можно значительно улучшить взаимодействие пользователя с приложением и предотвратить возможные ошибки.