Vue.js/JavaScript:如何将文本引用转为应用内链接并避免转义?
这个问题我之前做项目时也踩过坑!本质是你用的渲染环境(不管是原生JS还是React/Vue这类框架)默认会对插入的文本做转义处理,防止XSS攻击,但这就导致你生成的锚点HTML代码被当成纯文本显示出来了。下面分几种常见场景给你靠谱的解决方案:
原生JavaScript场景
如果是纯前端原生开发,直接用innerHTML来插入处理后的HTML内容,而不是textContent或者innerText——后两者会把所有内容当成纯文本解析。
示例代码:
// 从数据库获取的原始文本 const rawText = "查看[用户中心]了解更多,或者访问[设置页面]修改偏好"; // 正则替换[xxx]为应用内链接 const processedHtml = rawText.replace(/\[([^\]]+)\]/g, (match, linkText) => { // 这里根据链接文本映射到对应的应用内路由,你可以根据实际需求扩展这个映射表 const routeMap = { "用户中心": "/user-profile", "设置页面": "/app-settings" }; const targetUrl = routeMap[linkText] || "#"; // 找不到对应路由就跳转到锚点 return `<a href="${targetUrl}">${linkText}</a>`; }); // 把处理后的HTML插入到页面元素中 document.getElementById("content-container").innerHTML = processedHtml;
React场景
React默认会转义JSX中的字符串内容,所以直接把HTML字符串丢进去会被转义。这里有两种处理方式:
方式一:使用dangerouslySetInnerHTML
虽然名字看起来有点吓人,但只要你的内容是可信的(比如来自自己数据库的可控内容,不是用户提交的未知内容),就可以安全使用:
function ContentDisplay() { const rawText = "查看[用户中心]了解更多,或者访问[设置页面]修改偏好"; const processedHtml = rawText.replace(/\[([^\]]+)\]/g, (match, linkText) => { const routeMap = { "用户中心": "/user-profile", "设置页面": "/app-settings" }; const targetUrl = routeMap[linkText] || "#"; return `<a href="${targetUrl}">${linkText}</a>`; }); // 通过dangerouslySetInnerHTML直接渲染HTML return <div dangerouslySetInnerHTML={{ __html: processedHtml }} />; }
方式二:拆分文本为React元素(推荐)
如果用React Router做路由,更推荐用<Link>组件代替原生<a>标签,这时候可以把原始文本拆分成普通文本和链接节点的数组,再逐个渲染:
import { Link } from 'react-router-dom'; function ContentDisplay() { const rawText = "查看[用户中心]了解更多,或者访问[设置页面]修改偏好"; const routeMap = { "用户中心": "/user-profile", "设置页面": "/app-settings" }; // 拆分文本,把[xxx]作为单独的部分提取出来 const textParts = rawText.split(/(\[[^\]]+\])/); // 把每个部分转换成React元素 const renderedElements = textParts.map((part, index) => { if (part.startsWith('[') && part.endsWith(']')) { const linkText = part.slice(1, -1); const targetRoute = routeMap[linkText] || "#"; return <Link key={index} to={targetRoute}>{linkText}</Link>; } return <span key={index}>{part}</span>; }); return <div>{renderedElements}</div>; }
这种方式更符合React的组件化思想,也避免了使用dangerouslySetInnerHTML。
Vue场景
Vue的{{ }}插值语法默认会转义内容,所以需要用v-html指令来渲染HTML:
方式一:使用v-html
<template> <div v-html="processedHtml"></div> </template> <script> export default { data() { return { rawText: "查看[用户中心]了解更多,或者访问[设置页面]修改偏好", routeMap: { "用户中心": "/user-profile", "设置页面": "/app-settings" } }; }, computed: { processedHtml() { return this.rawText.replace(/\[([^\]]+)\]/g, (match, linkText) => { const targetUrl = this.routeMap[linkText] || "#"; return `<a href="${targetUrl}">${linkText}</a>`; }); } } }; </script>
方式二:拆分文本为Vue节点(推荐)
如果用Vue Router,同样推荐用<router-link>组件,拆分文本后渲染:
<template> <div> <template v-for="(part, index) in processedParts" :key="index"> <router-link v-if="part.isLink" :to="part.url">{{ part.text }}</router-link> <span v-else>{{ part.text }}</span> </template> </div> </template> <script> export default { data() { return { rawText: "查看[用户中心]了解更多,或者访问[设置页面]修改偏好", routeMap: { "用户中心": "/user-profile", "设置页面": "/app-settings" } }; }, computed: { processedParts() { const textParts = this.rawText.split(/(\[[^\]]+\])/); return textParts.map(part => { if (part.startsWith('[') && part.endsWith(']')) { const linkText = part.slice(1, -1); return { isLink: true, text: linkText, url: this.routeMap[linkText] || "#" }; } return { isLink: false, text: part }; }); } } }; </script>
总结
核心思路就是:不要让渲染环境把你生成的链接代码当成纯文本处理。如果是原生环境就用innerHTML;如果是框架环境,要么用框架提供的HTML渲染指令(比如React的dangerouslySetInnerHTML、Vue的v-html),要么更优雅地拆分文本为框架支持的组件/节点,后者在框架开发中更推荐,也更安全。
内容的提问来源于stack exchange,提问作者Tomás Ferreira

