一、简介

官方文档:

在这里插入图片描述
通过上面官方文档的介绍我们可以得知两个重点

① 通过v-html生成的页面元素,不会被当做Vue模板进行编译,只会作为普通的html代码被插入,也就是说通过v-html插入的html代码中,如果包含vue的语法,例如:@clickv-if等,则不会生效,因为他们没有被Vue编译,而浏览器并不会识别这些vue语法,所以这些语法都不生效。

② 在.vue文件中如果style标签上增加了scoped属性,那么该标签内的样式就不会对v-html生成的页面元素起作用,还是因为这些元素没有被当做Vue模板进行编译,所以不生效。

如果在某些业务场景下我们给v-html生成的页面元素绑定了@click事件,需要触发某些处理方法,或者需要给这些元素设置CSS样式时,这两个问题就比较致命了,所以我们要想办法去解决这俩问题。

解决绑定事件失效方法有:使用onclick等原生事件代替@click等事件vue事件、利用事件委托触发事件、使用component模板代替v-html

解决样式不生效的方法有:/deep/::v-deep>>>:deep、额外的全局<style>

二、解决@click等vue事件不触发

1、使用onclick等原生事件代替@click等vue事件

思路: vue事件之所以不能被触发是因为,v-html生成的页面元素,是在Vue文件编译之后插入到页面中的,不会被Vue编译,只会作为普通的html代码被插入,所以无法识别vue事件。既然是普通的html代码,那我们可以使用原生的事件去代替vue事件,原生事件一定会被触发,但此时又出现一个新问题,那就是原生事件被触发后,无法访问到vue实例的data中的数据和methods中的方法,究其原因还是由于没有被vue编译的问题。
既然问题出现了,那就想办法解决问题。既然访问不到vue实例中的数据和方法,那么我们可以将事件所需要使用的数据和方法,挂载在window对象上,这样就可以被原生事件访问到了。

具体代码:
<template>
  <div class="hello">
  	<!-- 渲染富文本数据 -->
    <div class="box" v-html="html">
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
    // 富文本数据  将原来的@click改为使用onclick
      html: '<div>这是v-html渲染的元素 <button οnclick="btnClick()">点击按钮</button></div>',
      a: '这是个变量'
      
    }
  },
  mounted() {
   // 将vue实例的方法绑定到window对象中去 
    window.btnClick = this.btnClick
  },
  methods: {
    btnClick() {
      alert('点击事件触发成功 + '+ this.a)
    }
  }
}
</script>

调用结果:

在这里插入图片描述

结论: 这种方法在一定程度上解决了v-html渲染元素不能触发@click的问题,但同样限制条件也比较多,比如:如果通过v-for同时渲染大量相似数据,无法访问当前循环项的数据,也就无法区分不同循环项的数据。所以这种方法只适用于绑定比较简单的事件处理操作。

2、利用事件委托触发事件

思路: 这种方法的原理是将子元素要触发执行的事件绑定到父元素上,点击子元素触发事件将会冒泡到父元素,然后父元素去执行对应的事件处理函数,但是要注意在事件处理函数中通过e.target去判断,触发事件的元素是不是我们想要绑定的那个元素,只有是目标子元素时,才去执行事件处理逻辑。

具体代码:
<template>
  <div class="hello">
    <!-- 将事件处理程序绑定到父元素上 利用冒泡原理 -->
    <div class="box" v-html="html" @click="btnClick($event)">
    </div>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      // 富文本数据 目标子元素为button按钮 
      html: '<div>这是通过v-html渲染的元素 <button id="btn">点击按钮</button></div>',
      a: '这是个变量'
    }
  },
  methods: {
    btnClick(e) {
    // 判断当前触发事件的元素 是不是目标子元素
    if(e.target.id === 'btn')
      alert('点击按钮事件触发成功 + '+ this.a)
    }
  }
}
</script>
调用结果:

在这里插入图片描述
结论: 这种方法比较好,不需要对富文本数据进行修改,也不需要修改window对象,比较推荐。

3、使用component模板代替v-html

思路: 前面我们已经得知@click等vue时间之所以不起作用,是因为v-html渲染的数据是在不会被当做Vue模板进行编译,只会作为普通的html代码被插入,那我们就想一种可以被当成Vue模板的渲染方式,那就是通过component模板来进行渲染。需要引入vue,通过Vue.extend(options) 基础Vue构造器,创建一个“子类”,然后通过vm.$mount()将实例进行编译,最后通过原生js进行挂载。

具体代码:
<template>
  <div class="hello">
    <div>
        父组件
        <!-- 用来渲染html数据的父组件 -->
        <div class='parent' id='parent'></div>
    </div>
  </div>
</template>

<script>
import Vue from 'vue'
export default {
  name: 'HelloWorld',
  data() {
    return {
      a: '这是a变量'
    }
  },
  mounted() {
  // 使用变量暂存一下this指向的vue实例
  var that = this;
  // 通过Vue.extend创建子类组件
   var MyComponent = Vue.extend({
        template: '<div>这是通过v-html渲染的元素 <button @click="add()">点击按钮</button></div>',
        methods: {     
          add() {
            // 通过暂存的that获取当前实例的数据和方法
             alert('触发了点击事件,并访问当时vue实例的变量a: ' + that.a)
             that.btnClick()
          }
      }
  })
  // 通过 $mount() 将子类组件进行编译
  let component=new MyComponent().$mount()
  // 通过原生js进行挂载
  document.getElementById('parent').appendChild(component.$el);
 },
 methods: {
   btnClick() {
     alert('点击按钮触发当时vue实例成功')
   }
 }
}
</script>
调用结果:

在这里插入图片描述
在这里插入图片描述
结论: 这种方法看起来比较高端,确实也比较好用,但是不适用于大量数据的渲染,因为会创建过多的vue子类,占用大量内存。

3、解决样式不生效的问题

请查看:vue 之 CSS进行样式穿透的方法(/deep/、::v-deep、>>> 、:deep、额外的全局<style>)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐