Vue 3 中 computed 实现搜索高亮的核心是按关键词位置精准切片再拼接,而非简单字符串替换;需用 exec 循环获取匹配位置、slice 拆分文本、安全组装 mark 片段,并处理空关键词、XSS 防护与多词转义。

Vue 3 中用 computed 实现搜索高亮,关键不是“替换字符串”,而是把原始文本按关键词位置精准切片,再拼接带 <mark></mark> 的 HTML 片段。整个过程必须保持响应式纯净、避免 XSS、支持空关键词兜底。
核心思路:用 exec 遍历匹配位置,slice 拆分再组装
不能用 String.replace() 简单粗暴替换,否则无法控制高亮粒度,也难处理重叠或边界情况。正确做法是:
- 用
RegExp.exec()循环获取每个匹配项的index和[0](完整匹配)、[1](捕获组) - 用
str.slice(currentIndex, result.index)提取关键词前的普通文本 - 用
result[0]作为高亮部分,包裹<mark>...</mark> - 更新
currentIndex = result.index + result[0].length,继续下一轮 - 循环结束后,补上最后一段剩余文本(
str.slice(currentIndex))
computed 封装示例:返回安全的高亮片段数组
以下是一个可直接复用的计算属性写法(支持多关键词、大小写不敏感、空关键词防护):
computed: {
highlightedParts() {
const text = this.rawText || '';
const keyword = this.searchKeyword?.trim() || '';
<pre class="brush:php;toolbar:false;">if (!keyword) return [{ type: 'text', content: text }];
// 转义正则特殊字符,支持关键词含 . * ? 等
const escaped = keyword.replace(/[.*+?^${}()|[]\]/g, '\$&');
const regex = new RegExp(`(${escaped})`, 'gi');
const parts = [];
let lastIndex = 0;
let match;
while ((match = regex.exec(text)) !== null) {
const [fullMatch] = match;
const matchStart = match.index;
// 普通文本段(匹配前)
if (matchStart > lastIndex) {
parts.push({ type: 'text', content: text.slice(lastIndex, matchStart) });
}
// 高亮文本段
parts.push({ type: 'highlight', content: fullMatch });
lastIndex = matchStart + fullMatch.length;
}
// 补尾部普通文本
if (lastIndex < text.length) {
parts.push({ type: 'text', content: text.slice(lastIndex) });
}
return parts;} }
模板中可配合 v-for 渲染:
立即学习“前端免费学习笔记(深入)”;
<span v-for="(part, i) in highlightedParts" :key="i">
<mark v-if="part.type === 'highlight'">{{ part.content }}</mark>
<span v-else>{{ part.content }}</span>
</span>必须注意的安全与兼容细节
直接拼 HTML 再用 v-html 有风险,而上面的分段数组方案天然规避了 XSS:
- 原始文本未被当作 HTML 解析,只在
v-else分支中作为纯文本插入 -
<mark></mark>是语义化内联标签,浏览器默认样式安全,不执行 JS - 若原始字段本身含用户输入的 HTML(如富文本),应在传入
rawText前先做基础转义(例如用DOMPurify.sanitize()或轻量函数转义为 <code>) - 多关键词时,把多个词用
|拼进正则,但需确保每个词都已转义,避免正则语法错误
性能优化建议(大文本/高频输入场景)
当 rawText 很长或 searchKeyword 频繁变化时,避免每次重算卡顿:
- 对搜索框使用
debounce(如lodash.debounce),延迟 200ms 触发关键词更新 - 限制高亮范围:只处理前 300 字符,用
text.slice(0, 300)入参,避免正则回溯爆炸 - 加缓存逻辑:仅当
rawText和searchKeyword同时未变时,复用上次结果(可用ref+JSON.stringify简单比对)


















