У меня возникла проблема с пользовательской директивой в проекте, которая предназначена для форматирования вывода цены. При первом рендере директива работает корректно, однако при реактивном обновлении значения свойств она не отображает изменённое значение. Если я просто вывожу значение без использования директивы, всё работает как надо.
Вот пример подключения директивы:
<pre><code class="javascript">// Подключенная директива
app.directive('price', PriceDirective)</code></pre>
А вот код самой директивы:
<pre><code class="javascript">// Сам файл с директивой
export default (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.replaceWith(r)
}</code></pre>
И вот как я использую директиву в шаблоне:
<pre><code class="javascript">// В самом шаблоне:
<span v-price="model.price" /> руб.</code></pre>
Как можно решить эту проблему с обновлением значения в директиве?
Проблема с тем, что директива не обновляет выводимое значение при изменении реактивного свойства, происходит из-за того, что вы не используете механизм реактивности 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 выводит.