предотвратить взаимодействие просмотренных элементов при вызове обновлений друг на друге

Вопрос или проблема

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

Оцените материал
Добавить комментарий

Капча загружается...