Почему директива не обновляется в режиме реального времени?

У меня возникла проблема с пользовательской директивой в проекте, которая предназначена для форматирования вывода цены. При первом рендере директива работает корректно, однако при реактивном обновлении значения свойств она не отображает изменённое значение. Если я просто вывожу значение без использования директивы, всё работает как надо. 

Вот пример подключения директивы:

<pre><code class="javascript">// Подключенная директива
app.directive('price', PriceDirective)</code></pre>

А вот код самой директивы:

<pre><code class="javascript">// Сам файл с директивой
export default (el, binding) =&gt; {
    let _number = binding.value,
        _decimal = (binding?.arg?.decimal) ? binding.arg.decimal : 0,
        _separator = ' ';
    let decimal = (typeof (_decimal) != 'undefined') ? _decimal : "";
    let separator = (typeof (_separator) != 'undefined') ? _separator : ' ';
    let r = parseFloat(_number), rr = 0, b = 0;
    let exp10 = Math.pow(10, decimal);
    r = Math.round(r * exp10) / exp10;
    rr = Number(r).toFixed(decimal).toString().split('.');
    b = rr[0].replace(/(\d{1,3}(?=(\d{3})+(?:\.\d|\b)))/g, "\$1" + separator);
    r = (rr[1] ? b + '.' + rr[1] : b);
    el.replaceWith(r)
}</code></pre>

И вот как я использую директиву в шаблоне:

<pre><code class="javascript">// В самом шаблоне:
&lt;span v-price="model.price" /&gt; руб.</code></pre>

Как можно решить эту проблему с обновлением значения в директиве?
  • 3 марта 2025 г. 13:44
Ответы на вопрос 2
Проблема с тем, что директива не обновляет выводимое значение при изменении реактивного свойства, происходит из-за того, что вы не используете механизм реактивности Vue в вашей директиве. Ваша текущая реализация директивы выполняется только один раз при первом рендеринге, но не следит за изменениями значения, переданного в директиву.

Для того чтобы директива обновлялась при изменении значения, вам нужно воспользоваться `update` хук, который вызывается каждый раз, когда изменяется значение, привязанное к директиве.

Вот как вы можете изменить вашу директиву, чтобы она работала с реактивными данными:

```javascript
export default {
    beforeMount(el, binding) {
        updatePrice(el, binding);
    },
    updated(el, binding) {
        updatePrice(el, binding);
    }
};

function updatePrice(el, binding) {
    let _number = binding.value,
        _decimal = (binding?.arg?.decimal) ? binding.arg.decimal : 0,
        _separator = ' ';
        
    let decimal = (typeof (_decimal) !== 'undefined') ? _decimal : "";
    let separator = (typeof (_separator) !== 'undefined') ? _separator : ' ';
    
    let r = parseFloat(_number), rr = 0, b = 0;
    let exp10 = Math.pow(10, decimal);
    r = Math.round(r * exp10) / exp10;
    
    rr = Number(r).toFixed(decimal).toString().split('.');
    b = rr[0].replace(/(\d{1,3}(?=(\d{3})+(?:\.\d|\b)))/g, "$1" + separator);
    r = (rr[1] ? b + '.' + rr[1] : b);
    
    el.textContent = r; // Обновляем содержимое элемента, а не replaceWith
}
```

В этом примере мы используем два метода: `beforeMount`, который выполняется один раз перед монтированием элемента, и `updated`, который вызывается каждый раз, когда реактивное значение вносится в директиву и меняется.

- `beforeMount` используется, чтобы установить первоначальное значение.
- `updated` используется, чтобы обновить значение каждый раз, когда `binding.value` изменяется.

Примечание: Вместо `el.replaceWith(r)` мы используем `el.textContent = r`, чтобы обновлять текстовое содержимое элемента, что более эффективно в данном контексте.
Вероятно потому что элемента  больше нет, есть текст. Надо избегать таких манипуляций с DOM, вся связь дерева с реактивными свойствами может сломаться вплоть до полной потери реактивности из-за ошибки выполнения js. 
ИМХО, правильно в данном случае сделать компонент для вывода и render функцию, возвращающую строку (если оно без тега должно быть). Но я бы на тегах не экономил, пусть компонент span выводит.
Похожие вопросы