Динамические элементы html в Vue.Яш

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

<template>
    {{{ message | hashTags }}}
</template>

<script>
    export default {
        ...

        filters: {
            hashTags: function(value) {
                // Replace hash tags with links
                return value.replace(/#(S*)/g, '<a v-on:click="someAction()">#</a>')
            }
        }
    }
</script>

проблема в том, что если я нажму ссылку, никакое действие не сработает. Vue не видят новых элементов.

4 ответов


обновление: На основе ответ, вы можете сделать аналогичный компонент динамического шаблона в Vue 2. Вы можете фактически настроить спецификацию компонента в computed раздел и связать его с помощью :is

var v = new Vue({
  el: '#vue',
  data: {
    message: 'hi #linky'
  },
  computed: {
    dynamicComponent: function() {
      return {
        template: `<div>${this.hashTags(this.message)}</div>`,
        methods: {
          someAction() {
            console.log("Action!");
          }
        }
      }
    }
  },
  methods: {
    hashTags: function(value) {
      // Replace hash tags with links
      return value.replace(/#(\S*)/g, '<a v-on:click="someAction">#</a>')
    }
  }
});

setTimeout(() => {
  v.message = 'another #thing';
}, 2000);
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="vue">
  <component :is="dynamicComponent" />
</div>

привязки Vue не происходят на интерполированном HTML. Вам нужно что-то, что Vue видит как шаблон, например частичная. Однако Vue применяет привязки только к частичному один раз; вы не удается вернуться и изменить текст шаблона и повторно привязать его. Таким образом, каждый раз, когда текст шаблона изменяется, вы должны создать новый частичный.

есть <partial> tag / element вы можете поместить в свой HTML, и он принимает имя переменной, поэтому процедура:

  • шаблон HTML изменяется
  • зарегистрировать новое частичное имя для нового шаблона HTML
  • обновить переменную имени, чтобы новая частичная отображалась

это немного ужасно регистрировать что-то новое каждый раз, когда есть изменения, поэтому было бы предпочтительнее использовать компонент с более структурированным шаблоном, если это возможно, но если вам действительно нужен полностью динамический HTML с привязками, он работает.

пример ниже начинается с одного сообщения, ссылка-ified в соответствии с вашим фильтром, и через две секунды, изменения message.

вы можете просто использовать message как имя частичного для регистрации, но вам нужен вычисленный, который возвращает это имя после регистрации, в противном случае оно попытается отобразить до регистрации имени.

var v = new Vue({
  el: 'body',
  data: {
    message: 'hi #linky'
  },
  computed: {
    partialName: function() {
      Vue.partial(this.message, this.hashTags(this.message));
      return this.message;
    }
  },
  methods: {
    someAction: function() {
      console.log('Action!');
    },
    hashTags: function(value) {
      // Replace hash tags with links
      return value.replace(/#(\S*)/g, '<a v-on:click="someAction()">#</a>')
    }
  }
});

setTimeout(() => {
  v.$set('message', 'another #thing');
}, 2000);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<partial :name="partialName"></partial>

Я только что узнал о $compile, и это, кажется, очень хорошо соответствует вашей потребности. Очень простая директива с использованием $compile избегает всех регистраций.

Vue.directive('dynamic', function(newValue) {
    this.el.innerHTML = newValue;
    this.vm.$compile(this.el);
});

var v = new Vue({
  el: 'body',
  data: {
    message: 'hi #linky'
  },
  computed: {
    messageAsHtml: function() {
      return this.message.replace(/#(\S*)/g, '<a v-on:click="someAction()">#</a>');
    }
  },
  methods: {
    someAction: function() {
      console.log('Action!');
    }
  }
});

setTimeout(() => {
  v.$set('message', 'another #thing');
}, 2000);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div v-dynamic="messageAsHtml"></div>

С partial был удален из VueJS 2 (https://vuejs.org/v2/guide/migration.html#Vue-partial-removed)

лучшим способом может быть создание компонента, который обрабатывает его содержимое и создает соответствующие элементы DOM

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

<process-text>Hi #hashtag !</process-text>
Vue.component('process-text', {
    render: function (createElement) {
        var hashtagRegex = /(^|\W)(#[a-z\d][\w-]*)/ig
        var text = this.$slots.default[0].text
        var list = text.split(hashtagRegex)
        var children = []
        for (var i = 0; i < list.length; i++) {
            var element = list[i]
            if (element.match(hashtagRegex)) {
                children.push(createElement('a', {
                attrs: {
                    href: 'https://www.google.fr/search?q=' + element,
                    target: "_blank"
                    },
                domProps: {
                    innerHTML: element
                    }
                }))
            } else {
                children.push(element)
            }
        }
    }
    return createElement('p', {}, children)  // VueJS expects root element
})

лучшее решение, которое я нашел, которое отлично работает с пользовательским html, выглядит так, как будто вы создаете новый компонент каждый раз, когда изменяется свойство html. На самом деле никто этого не делал, мы просто используем вычисляемое свойство для создания нового компонента.

вот как это выглядит:

new Vue({
  el: "#root",
  data: {
      value: '',
      name: 'root',
      htmlData: '<div><input @input="onInputProxy($event)" ' +
                            'v-model="value" ' + 
                            'v-for="i in 3" ' + 
                            ':ref="`customInput${i}`"></div>'
  },
  computed: {
    // our component is computed property which returns the dict
    htmlDataComponent () {
      return {
        template: this.htmlData, // we use htmlData as template text

        data() {
          return {
            name: 'component',
            value: ''
          }
        },
        created () {
          // value of "this" is formComponent
          console.log(this.name + ' created');
        },
        methods: {
          // proxy components method to parent method,
          // actually you done have to
          onInputProxy: this.onInput
        }
      }
    }
  },
  methods: {
    onInput ($event) {
      // while $event is proxied from dynamic formComponent
      // value of "this" is parent component
      console.log(this.name + ' onInput');

      // use refs to refer to real components value
      console.log(this.$refs.htmlDataComponent.value);
      console.log(this.$refs.htmlDataComponent.$refs.customInput1);
      console.log(this.$refs.htmlDataComponent.$refs.customInput2);
      console.log(this.$refs.htmlDataComponent.$refs.customInput3);
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js">
</script>

<div id="root">
  <component ref="htmlDataComponent" 
             v-if="htmlData"
             :is="htmlDataComponent"></component>
</div>

Я не проверить его на эффективность памяти, но, похоже, работает просто отлично.