目录
- 方案 1:后端返回带有标记的文本
- 方案 2:前端处理高亮显示(后端只返回纯文本)
- 方案 3:后端返回匹配位置,前端负责高亮
- 方案选择的考量:
在前端实现关键词检索后的高亮展示以及前后端接口设计时,通常有几种可选方案。以下是一些推荐的实现方案,分别涵盖了前后端设计和前端的高亮展示:
方案 1:后端返回带有标记的文本
-
后端接口设计:
- 在后端处理搜索逻辑时,直接将匹配到的
keyword
用特定标记(例如<mark>
标签或自定义标记符号)包裹,返回的数据中包含带有标记的title
和description
。 - 返回的数据结构可能类似这样:
[ { "title": "This is a <mark>keyword</mark> example", "description": "Find the <mark>keyword</mark> in this description" } ]
- 在后端处理搜索逻辑时,直接将匹配到的
-
前端渲染:
- 前端可以直接使用
dangerouslySetInnerHTML
渲染后端返回的标记内容,来展示高亮效果。
const renderTextWithHighlight = (text: string) => { return <span dangerouslySetInnerHTML={{ __html: text }} />; }; const SearchResults = ({ results }) => { return results.map(item => ( <div key={item.title}> <h3>{renderTextWithHighlight(item.title)}</h3> <p>{renderTextWithHighlight(item.description)}</p> </div> )); };
- 前端可以直接使用
方案 2:前端处理高亮显示(后端只返回纯文本)
-
后端接口设计:
- 后端只返回普通的
title
和description
字段,不带任何高亮标记。返回结构例如:[ { "title": "This is a keyword example", "description": "Find the keyword in this description" } ]
- 后端只返回普通的
-
前端渲染:
- 前端接收数据后,自己在渲染时根据
keyword
进行字符串匹配,并使用 React 组件去动态生成包含高亮的文本片段。 - 可以通过
split
方法分割匹配到的keyword
,然后用<span>
包裹高亮的部分。例如:
const highlightText = (text: string, keyword: string) => { const parts = text.split(new RegExp(`(${keyword})`, 'gi')); return ( <span> {parts.map((part, i) => part.toLowerCase() === keyword.toLowerCase() ? ( <span key={i} style={{ color: 'red' }}>{part}</span> ) : ( part ) )} </span> ); }; const SearchResults = ({ results, keyword }) => { return results.map(item => ( <div key={item.title}> <h3>{highlightText(item.title, keyword)}</h3> <p>{highlightText(item.description, keyword)}</p> </div> )); };
- 前端接收数据后,自己在渲染时根据
方案 3:后端返回匹配位置,前端负责高亮
-
后端接口设计:
- 后端除了返回
title
和description
外,还返回每个字段中匹配的keyword
的位置,方便前端处理高亮。返回结构例如:[ { "title": "This is a keyword example", "description": "Find the keyword in this description", "matches": { "title": [10, 17], "description": [9, 16] } } ]
- 后端除了返回
-
前端渲染:
- 前端根据后端返回的匹配位置来生成高亮的文本片段,使用
substring
方法或者字符分割的方式在指定位置应用高亮:
const highlightTextByPositions = (text: string, positions: number[]) => { const parts = []; let lastIndex = 0; for (let i = 0; i < positions.length; i += 2) { const start = positions[i]; const end = positions[i + 1]; parts.push(text.substring(lastIndex, start)); parts.push(<span style={{ color: 'red' }}>{text.substring(start, end)}</span>); lastIndex = end; } parts.push(text.substring(lastIndex)); return <span>{parts}</span>; }; const SearchResults = ({ results }) => { return results.map(item => ( <div key={item.title}> <h3>{highlightTextByPositions(item.title, item.matches.title)}</h3> <p>{highlightTextByPositions(item.description, item.matches.description)}</p> </div> )); };
- 前端根据后端返回的匹配位置来生成高亮的文本片段,使用
方案选择的考量:
- 方案 1(后端处理高亮):这种方式后端负担较大,前端只负责渲染标记好的内容。适合对 SEO 或者后端内容展示有特殊要求的情况,但需要注意
dangerouslySetInnerHTML
的使用安全性。 - 方案 2(前端处理高亮):前端处理高亮逻辑更加灵活,后端无需改动。适合前端性能需求较高但内容不会太大的场景。
- 方案 3(后端返回匹配位置,前端高亮):这种方式前后端工作相对均衡,但需要增加后端逻辑去精确返回匹配的位置。
根据你的需求,前后端的技术栈、性能要求,以及团队协作分工,可以在这些方案中选择最适合的一种。如果搜索结果较多或复杂,可以考虑后端返回带有标记或匹配位置的信息,减少前端计算的复杂度。