Vue如何动态修改template?

先上代码:

<template>
    <div v-html="marked(content)"></div>
</template>
<script>
....
....
methods:{
   markdown (content) {
      var renderer = new marked.Renderer()
      renderer.image = (href, title, text) => {
        return '<a @click="showFullImage">' + text + '</a>'
      }
      marked.setOptions({
        renderer: renderer,
        gfm: true,
        tables: true,
        breaks: true,
        pedantic: false,
        sanitize: false,
        smartLists: true,
        smartypants: false,
        highlight: (code) => {
          return hljs.highlightAuto(code).value
        }
      })
      return marked(content)
   }
}
</script>

我在使用vue做一个工具,需要输入框中的markdown代码转为html,然后展示出来。
其中我想做的一个功能是,当用户输入:

![pic](www.example.com/img.jpg "title")

使用marked的自定义渲染函数,转换成html(已经实现):

<a @click="showFullImage">title</a>'

现在的问题是,在展示的时候,使用v-html无法完成函数绑定。各位大神有没有好的方法?

简洁的说法是,我需要自己生成一段vue格式的html字符串,然后将这个字符串渲染出来,里面有各种vue指令需要绑定,该怎么做呢?

阅读 30.5k
7 个回答

@SayGoodBey ,恰好看到你问了,那就写出我的方法吧。
我是在一个子组件中实现的,你可以动态的添加该子组件:
下面的content是markdown格式的数据,
../common/markdown文件是自己写好的基于marked的解析函数,它会将Markdown格式析为Vue格式的字符串:

![图片文字](url)
// 上面会解析为:
<img src="url" @click="showInfo('图片文字')">

用下面的方法即可以实现点击图片时,会输出信息。当然其他的vue处理方法同样支持。

<template >
  <div ref="markedContent"></div>
</template>

<script>
import Vue from 'vue'
import markdown from '../common/markdown'
export default {
  name: 'wf-marked-content',
  props: ['content'],
  mounted () {

    // 调用compile方法。你也可以将写在这里。
    // 但是代码太多,我个人不喜欢
    this.compile()
  },
  methods: {
    compile () {
      // 变量html是生成好的vue格式的HTML模板字符串,
      // 这个模板里面可以包含各种vue的指令,数据绑定等操作,
      // 比如 v-if, :bind, @click 等。
      const html = markdown(this.content)
      
      // Vue.extend是vue的组件构造器,专门用来构建自定义组件的,
      // 但是不会注册,类似于js中的createElement,
      // 创建但是不会添加。
      // 在这里创建出一个子组件对象构造器。
      const Component = Vue.extend({
      
        // 模板文件。由于Markdown解析之后可能会有多个根节点,
        // 因此需要包裹起来。
        // 实际的内容是:
        // `<div><img src="url" @click="showInfo(`图片文字')"></div>`
        template: `<div> ${html} </div>`,
        
        // 这里面写的就是这个动态生成的新组件中的方法了,
        // 当然你也可加上data、mounted、updated、watch、computed等等。
        methods: {
        
          // 上面模板中将点击事件绑定到了这里,因此点击了之后就会调用这个函数。
          // 你可以写多个函数在这里,但是这里的函数的作用域只限在这个子组件中。
          showInfo (title) {
            console.log(title)
          }
        }
      })
      
      // new Component()是将上面构建的组件对象给实例化,
      // $mount()是将实例化的组件进行手动挂载,
      // 将虚拟dom生成出实际渲染的dom,
      // 这里的markedComponent是完成挂载以后的子组件
      const markedComponent = new Component().$mount()
      
      // 将挂载以后的子组件dom插入到父组件中
      // markedComponent.$el就是挂载后生成的渲染dom
      this.$refs['markedContent'].appendChild(markedComponent.$el)
    }
  }
  // 本质上来讲,这个子组件不是任何组件的子组件,
  // 它是由vue直接在全局动态生成的一个匿名组件,然后将它插入到当前位置的。
  // 也正是因此,它才能够完成动态的生成和添加。
}
</script>

额,解决这个问题是为了写一个支持Markdown格式的评论系统。所以顺便在这里推荐一下:
Wildfire: A drop-in replacement for other comment plug-ins.
一定要点进去,好看又好用的评论系统。

请问楼主解决了吗?动态向template里添加vue组件不生效

新手上路,请多包涵
<template>
  <div>
    <component v-for="row in rows" :key="row.id" :is="getComponentName(row)" :row="row"></component>
  </div>
</template>

<script>
import Vue from 'vue'

export default {
  name: 'cmp-list-view',
  data () {
    return {
      rows: []
    }
  },
  created () {
    let rows = [
      { id: Math.ceil(Math.random() * 10000), name: '小明', template: '<div style="color:#409EFF">{{row.name}}</div>' },
      { id: Math.ceil(Math.random() * 10000), name: '小红', template: '<div style="color:#67C23A">{{row.name}}</div>' }
    ]

    for (let row of rows) {
      Vue.component(`cmp-${row.id}`, {
        props: ['row'],
        template: row.template
      })
    }

    this.rows = rows
  },
  methods: {
    getComponentName (row) {
      return `cmp-${row.id}`
    }
  }
}
</script>
新手上路,请多包涵

vue的原理是先编译模板 比如你自定义的控件 v-model="data.name" 写字符创放在模板中是允许的

如果data.name 是动态写法 将val="name" v-model="data["val"]" 就是不允许的。

模板在你注册组件的时候已经编译完成了,而不是你在调用组件时每次条用都进行一次编译。

javascript 好好的函数式编程就这么被预编译毁掉了。

angularejs是可以这么干的,但是上手难度较大。

我也遇到了一样的需求2333,用了一个比较野蛮的方法

<template>
    <div v-html="marked(content)"></div>
</template>
<script>
    export default {
        name: "someVue",
        created () {
            // created的时候将this绑定到一个全局变量
            window.someVue = this
        },
        methods: {
            markdown (content) {
                var renderer = new marked.Renderer()
                renderer.image = (href, title, text) => {
                    // 在你新建的元素上,绑定一个原生onclick事件,并将本来的href传入一个attr
                    return '<a href="javascript:;" onclick="windowShowFullImage(event)" full-image="' + href + '">' + text + '</a>'
                }
                return marked(content)
            }
        },
        showFullImage: function () {
            // 这里是你希望点击你新建的元素时执行的vue方法
        }
    }
    // 这里是你新建的元素绑定的原生函数
    window.windowShowFullImage = (e) => {
        // 调用全局变量里的这个vue的函数
        window.someVue.showFullImage()
    }
</script>
  1. 使用Vue.createElement创建一个新的vNode, 这个可以使用this.$createElement
  2. 用querySelector或者$refs.xxx的方式, 获取一个element
  3. 使用this.__patch__(el, vnode, false, true) 将vnode渲染到界面上.
<div id="div3">
    <component :is="currentView">
  <!-- 组件在 vm.currentview 变化时改变 -->
    </component>
</div>
 
<script>
    new Vue({
        el:"#div3",
        data: {
         currentView: 'component1'
        },
        components:{
            component1:{template:"<p>hello1</p>"},
            component2:{template:"<p>hello2</p>"},
            component3:{template:"<p>hello3</p>"}
        }
    });  
</script>

这里:is相当于v-bind:isis属性在前面用过,还记得不:

<tr is="my-component"></tr>

相当于:

<tr><my-component></my-component></tr>

只不过当时没有用v-bind,v-bind 指令用于响应地更新 HTML 特性,所以因为要切换组件所以用上。

渲染结果为:

<div id="div3">
    <p>hello1</p>
</div>
推荐问题
宣传栏