在Vue项目开发中,我们经常会遇到后端返回包含特定标记的字符串的场景,比如字符串里带有router-link标记,需要把这段字符串渲染成可点击的路由链接。直接用v-html渲染会有XSS风险,也不符合Vue的组件化开发理念,这时候就需要通过解析字符串、匹配动态组件的方式实现需求。

核心实现思路
整个实现过程可以分为三个核心步骤:
- 解析带有特定标记的字符串,拆分出普通文本和标记包裹的内容
- 建立标记和对应Vue组件的映射关系,比如把router-link标记映射到RouterLink组件
- 使用Vue的动态组件
component或者渲染函数,把解析后的内容渲染到页面上
准备工作
首先需要在项目中引入需要的组件,比如路由的RouterLink组件,同时定义好标记和组件的映射表:
import { RouterLink } from 'vue-router'
import { h } from 'vue'
// 定义标记和组件的映射关系,key是标记名称,value是对应的组件
const componentMap = {
'router-link': RouterLink
}
// 示例待解析的字符串,标记用方括号包裹,格式为 [标记名 属性键值对]内容[/标记名]
const rawString = '点击[router-link to="/home"]首页[/router-link]跳转,或者[router-link to="/about"]关于我们[/router-link]查看详情'字符串解析函数实现
我们需要写一个解析函数,把原始字符串拆分成普通文本和标记组件两部分,这里用正则表达式匹配标记结构:
/**
* 解析带有特定标记的字符串
* @param {string} str 原始字符串
* @returns {Array} 解析后的节点数组,每个节点是文本或者组件配置
*/
function parseStringWithTags(str) {
// 匹配标记的正则,格式为 [标记名 属性]内容[/标记名]
const tagReg = /\[(\w+(?:-\w+)*)(?:\s+([^\]]*))?\](.*?)\[\/\1\]/g
const nodes = []
let lastIndex = 0
let match
while ((match = tagReg.exec(str)) !== null) {
// 添加标记前面的普通文本
if (match.index > lastIndex) {
nodes.push({
type: 'text',
content: str.slice(lastIndex, match.index)
})
}
const tagName = match[1]
const attrStr = match[2] || ''
const content = match[3]
// 解析标记的属性,比如 to="/home" 这种键值对
const attrs = {}
if (attrStr) {
const attrReg = /(\w+)="([^"]*)"/g
let attrMatch
while ((attrMatch = attrReg.exec(attrStr)) !== null) {
attrs[attrMatch[1]] = attrMatch[2]
}
}
// 添加组件节点
nodes.push({
type: 'component',
tag: tagName,
attrs,
content
})
lastIndex = match.index + match[0].length
}
// 添加最后剩余的普通文本
if (lastIndex < str.length) {
nodes.push({
type: 'text',
content: str.slice(lastIndex)
})
}
return nodes
}渲染组件实现
解析完成后,我们可以写一个渲染组件,把解析后的节点渲染成实际的页面内容:
<template>
<div class="dynamic-string-renderer">
<template v-for="(node, index) in parsedNodes" :key="index">
<!-- 普通文本直接渲染 -->
<span v-if="node.type === 'text'">{{ node.content }}</span>
<!-- 组件节点用动态组件渲染 -->
<component
v-else
:is="componentMap[node.tag]"
v-bind="node.attrs"
>
{{ node.content }}
</component>
</template>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { RouterLink } from 'vue-router'
// 组件映射表
const componentMap = {
'router-link': RouterLink
}
// 接收父组件传入的原始字符串
const props = defineProps({
rawString: {
type: String,
required: true
}
})
// 解析字符串得到节点数组
const parsedNodes = computed(() => {
const str = props.rawString
const tagReg = /\[(\w+(?:-\w+)*)(?:\s+([^\]]*))?\](.*?)\[\/\1\]/g
const nodes = []
let lastIndex = 0
let match
while ((match = tagReg.exec(str)) !== null) {
if (match.index > lastIndex) {
nodes.push({
type: 'text',
content: str.slice(lastIndex, match.index)
})
}
const tagName = match[1]
const attrStr = match[2] || ''
const content = match[3]
const attrs = {}
if (attrStr) {
const attrReg = /(\w+)="([^"]*)"/g
let attrMatch
while ((attrMatch = attrReg.exec(attrStr)) !== null) {
attrs[attrMatch[1]] = attrMatch[2]
}
}
nodes.push({
type: 'component',
tag: tagName,
attrs,
content
})
lastIndex = match.index + match[0].length
}
if (lastIndex < str.length) {
nodes.push({
type: 'text',
content: str.slice(lastIndex)
})
}
return nodes
})
</script>使用示例
在页面中使用这个渲染组件,传入带有router-link标记的字符串即可:
<template>
<div>
<h3>动态渲染字符串中的组件</h3>
<DynamicStringRenderer
raw-string="欢迎访问,点击[router-link to="/home"]首页[/router-link]返回主页,或者[router-link to="/user"]个人中心[/router-link]查看信息"
/>
</div>
</template>
<script setup>
import DynamicStringRenderer from './components/DynamicStringRenderer.vue'
</script>注意事项
- 标记格式可以根据项目需求调整,正则匹配规则也要同步修改,比如如果标记用花括号包裹,就调整对应的正则
- 如果需要支持嵌套标记,比如
router-link里面包含其他组件标记,需要递归解析节点,上面的示例是基础版本,支持扩展 - 属性解析目前只支持键值对带双引号的形式,如果有其他属性格式,可以补充对应的正则匹配规则
- 如果不需要路由的
router-link,也可以把映射表里的组件换成自定义组件,实现任意动态组件的渲染
扩展:支持嵌套标记
如果需要支持标记嵌套,只需要把解析函数改成递归解析即可,比如解析[router-link to="/home"]点击[span class="red"]这里[/span]跳转[/router-link]这种结构:
function parseNode(str, start = 0, end = str.length) {
const nodes = []
let lastIndex = start
const tagReg = /\[(\w+(?:-\w+)*)(?:\s+([^\]]*))?\](.*?)\[\/\1\]/g
let match
// 重置正则的lastIndex,避免匹配错误
tagReg.lastIndex = start
while ((match = tagReg.exec(str)) !== null && match.index < end) {
if (match.index > lastIndex) {
nodes.push({
type: 'text',
content: str.slice(lastIndex, match.index)
})
}
const tagName = match[1]
const attrStr = match[2] || ''
const contentStart = match.index + match[1].length + (attrStr ? attrStr.length + 2 : 0) + 2 // 跳过[tagname attr]
const contentEnd = match.index + match[0].length - tagName.length - 3 // 跳过[/tagname]
// 递归解析子内容
const children = parseNode(str, contentStart, contentEnd)
const attrs = {}
if (attrStr) {
const attrReg = /(\w+)="([^"]*)"/g
let attrMatch
while ((attrMatch = attrReg.exec(attrStr)) !== null) {
attrs[attrMatch[1]] = attrMatch[2]
}
}
nodes.push({
type: 'component',
tag: tagName,
attrs,
children
})
lastIndex = match.index + match[0].length
}
if (lastIndex < end) {
nodes.push({
type: 'text',
content: str.slice(lastIndex, end)
})
}
return nodes
}对应的渲染组件也需要调整,支持渲染子节点,这样就可以实现任意层级的标记嵌套渲染了。
Vue动态组件字符串解析router-link渲染修改时间:2026-06-02 05:10:01