Vue3循环渲染时v-html内@click、router-link无法正常渲染如何解决
问题本质
v-html 仅会将传入的字符串作为原生HTML插入DOM,全程不会触发Vue的模板编译流程,因此字符串内的事件指令、自定义组件、响应式绑定都会被当作普通原生内容处理,自然无法生效。这是Vue出于XSS安全防护做的默认设计,不是功能bug。
可行实现方案
根据场景可以二选一:
方案1:运行时编译字符串模板(最贴合现有代码逻辑)
Vue本身提供了运行时编译API,可以直接把字符串模板编译为可渲染的Vue组件,不需要硬编码每个列表项,改动成本最低。
- 首先调整构建配置,让项目引入带运行时编译器的Vue版本:
如果是Vite构建的项目,在vite.config.js中添加别名配置即可:
// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], resolve: { alias: { vue: 'vue/dist/vue.esm-bundler.js' } } })
- 改造业务代码,用
compile方法把字符串模板编译为渲染函数,通过动态组件渲染:
<template> {{ click }} <ol> <li v-for="(itemComp, idx) in compiledList" :key="idx"> <component :is="itemComp" /> </li> </ol> </template> <script setup> import { ref, compile } from 'vue' import { RouterLink } from 'vue-router' const click = ref(0) // 把模板中用到的响应式变量、组件统一放到作用域对象中 const renderScope = { click, RouterLink } const rawItems = [ '<p>text</p><a @click="click+=1">click</a>', '<p>text2</p><p><router-link :to="{name: \'home\'}">home</router-link></p>' ] // 批量将字符串模板编译为可渲染的组件配置 const compiledList = rawItems.map(tplStr => { // 包一层根节点避免多根节点编译报错 const render = compile(`<div>${tplStr}</div>`) return { render, data() { return renderScope }, components: { RouterLink } } }) </script>
注意:这个方案存在XSS风险,必须保证传入的字符串模板是可控的可信内容,绝对不能直接将用户提交的未过滤内容传入编译。
方案2:结构化配置渲染(无编译开销、更安全)
如果不想引入运行时编译器(会增加约10-15KB的包体积),可以放弃存纯HTML字符串的思路,改为存结构化的渲染配置,在模板中根据配置动态渲染对应内容,性能和安全性都更好,适合长期维护的项目:
<template> {{ click }} <ol> <li v-for="(item, idx) in items" :key="idx"> <p>{{ item.text }}</p> <a v-if="item.clickAction" @click="item.clickAction">{{ item.clickText }}</a> <p v-if="item.route"> <RouterLink :to="{ name: item.routeName }">{{ item.routeText }}</RouterLink> </p> </li> </ol> </template> <script setup> import { ref } from 'vue' const click = ref(0) // 结构化配置,不用写HTML字符串 const items = [ { text: 'text', clickAction: () => click.value += 1, clickText: 'click' }, { text: 'text2', routeName: 'home', routeText: 'home' } ] </script>
这个方案的缺点是如果模板结构非常灵活多变,需要提前约定好配置结构,灵活度比直接编译字符串稍差。
内容的提问来源于stack exchange,提问作者Oliver
相关产品推荐
相关产品推荐

