开发中常常遇到类似于搜索引擎一样的关键词着色,突出显示关键词比如标红, 以下只是解决问题的一两种方法, 当然方法有很多, 需要再进一步的思考
设定:

  • 搜索文本时高亮
  • 匹配替换(只允许在编辑情况下)
  1. const title = '关键词a, 关键词b, 关键词c, 其他剩余文本';
  2. // 关键词可以是以逗号分隔的字符串, 也可以是数组
  3. const keyWords = '关键词a, 关键词b';

解决方案

方案一

  1. /**
  2. * @desc: 主要使用数组循环以及正则匹配
  3. * @params: 返回替换后的字符串
  4. */
  5. function formateKeyWords() {
  6. // 此处判断是否为数组, 即少去一步
  7. const arr = keyWords.replace(/\s/g, '').split(',');
  8. let formtedTitle = title;
  9. arr.forEach(keyword => {
  10. if (formtedTitle.includes(keyword)) {
  11. formtedTitle = formtedTitle.replace(new RegExp(keyword, 'g'), '<span>'+keyword+'</span>');
  12. }
  13. });
  14. return formtedTitle;
  15. }

方案二

  1. /**
  2. * @desc: 单纯使用正则匹配
  3. * @params: 返回替换后的字符串
  4. */
  5. function formateKeyWords() {
  6. const trimKeywords = keyWords.replace(/\s+?/g, '');
  7. const r = new RegExp(trimKeywords.replace(/,/g, '|'));
  8. const r1 = new RegExp(r, 'g');
  9. const formatedTitle = title.replace(r1, match => '<span>'+ match +'</span>');
  10. return formatedTitle;
  11. }

方案三

方案三参考自网络, 有缺陷, 比如关键词是数组或者命中多个关键词时

参考

Mark.js
React-highlight-words

  1. /**
  2. * @name: formatKeyword
  3. * @description: 搜索关键字标红
  4. * @param: label: 原文本, value 关键词
  5. */
  6. import React from 'react';
  7. export default function formatKeyword(label, value) {
  8. if (!value) {
  9. return label;
  10. }
  11. return (
  12. <span>
  13. {label.split(value).reduce((prev, current, i) => {
  14. if (!i) {
  15. return [current];
  16. }
  17. return prev.concat(
  18. <span
  19. style={{ color: '#ff186d' }}
  20. key={value + current + Math.random() * 100}
  21. >
  22. {value}
  23. </span>,
  24. current,
  25. );
  26. }, [])}
  27. </span>
  28. );
  29. }

React

在React 中使用

  1. import React from 'react'
  2. // 无论使用哪种方法都可以, 这里使用第二种
  3. function LabelWithColor({ text, keyWords, color = 'red' }) {
  4. const trimKeywords = keyWords.replace(/\s+?/g, '');
  5. const r = new RegExp(trimKeywords.replace(/,/g, '|'));
  6. const r1 = new RegExp(r, 'g');
  7. const formatedTitle = text.replace(r1, match => `<span style="color: ${color}">${match}</span>`);
  8. return (
  9. <span dangerouslySetInnerHTML={{ __html: formatedTitle }} />
  10. );
  11. }

最终方案

  1. /**
  2. * @name: HighLightWord
  3. * @desc: 关键词高亮
  4. * @author NHF
  5. */
  6. import React, { Fragment } from 'react';
  7. interface DeepArray<T> extends Array<T | DeepArray<T>> { }
  8. type KeywordTypes = DeepArray<string | string[]> | string;
  9. type KeywordColor = string | { [key: string]: string };
  10. /** 统计记录匹配到字符开始 & 结束位置 */
  11. interface MatchRange {
  12. /** 开始位置: reg.index */
  13. start: number;
  14. /** 结束位置: reg.index + value.length */
  15. end: number;
  16. /** 匹配到的字符 */
  17. value: string;
  18. }
  19. export interface HighlightProps {
  20. className?: string;
  21. style?: React.CSSProperties;
  22. /** 展示字符串 */
  23. text: string;
  24. /** 配置的颜色 */
  25. color?: KeywordColor;
  26. /** 需要高亮的关键词 */
  27. keywords?: KeywordTypes;
  28. }
  29. const { toString } = Object.prototype;
  30. const DEFAULT_COLOR = '#f47d31';
  31. const isType = (val: any, type: string) => toString.call(val) === `[object ${type}]`;
  32. const isObject = (val: any) => isType(val, 'Object');
  33. const isString = (val: any) => isType(val, 'String');
  34. /** 去除空格 */
  35. const trimReg: RegExp = /[-[\]{}()*+?.,\\^$|#\s]/g;
  36. function trim(val: any) {
  37. return isString(val) ? val.replace(trimReg, '') : val;
  38. }
  39. function getTrimKeywords(val: KeywordTypes) {
  40. if (typeof val === 'string' && val.includes(',')) {
  41. return val.split(',').map(item => trim(item));
  42. }
  43. if (isString(val)) {
  44. return [trim(val)];
  45. }
  46. if (Array.isArray(val)) {
  47. // give me to your hand
  48. // don't need trim, should be fix
  49. // return val.map((item: any) => (isArray(item) ? getTrimKeywords(item) : trim(item)));
  50. return val.reduce((prev, next) => prev.concat(Array.isArray(next) ? getTrimKeywords(next) : trim(next)), [])
  51. }
  52. return [];
  53. }
  54. /** 获取到对应的正则 */
  55. function getKeywordsReg(keywords: KeywordTypes) {
  56. const $newkeywords = getTrimKeywords(keywords);
  57. const $reg = [... new Set($newkeywords)].join('|');
  58. const keywordsReg = new RegExp($reg, 'ig');
  59. return keywordsReg;
  60. }
  61. /** 获取到对应的颜色 */
  62. function getColorByMatch(match: string, color?: { [key: string]: string } | string) {
  63. if (isObject(color)) {
  64. return color[match] || DEFAULT_COLOR;
  65. }
  66. return color;
  67. }
  68. /** 统计匹配字符位置及其他信息 */
  69. const getMatchMap = (text: string, reg: RegExp) => {
  70. const map = new Map();
  71. const range = Array.from(text.matchAll(reg));
  72. range.forEach(item => {
  73. map.set(item.index, {
  74. value: item[0],
  75. start: item.index,
  76. end: item.index + item[0].length
  77. });
  78. });
  79. return map;
  80. }
  81. /** 分隔字符串 拼接 */
  82. const getLastContent = (splited: string[], matchMap: Map<number, MatchRange>, color) => {
  83. /** 统计使用 */
  84. let count = 0;
  85. const list = splited.reduce((prev, next) => {
  86. count += next.length;
  87. prev.push(<Fragment key={count}>{next}</Fragment>);
  88. if (matchMap.has(count) && matchMap.get(count).start === count) {
  89. const matched = matchMap.get(count);
  90. count = matched.end;
  91. prev.push(<span key={`${count}-${matched.start}-${matched.end}`} style={{ color: getColorByMatch(matched.value, color) }}>{matched.value}</span>);
  92. }
  93. return prev;
  94. }, []);
  95. return list;
  96. }
  97. const HighLightWord: React.FunctionComponent<HighlightProps> = React.memo<HighlightProps>((props) => {
  98. const { text, color, style, className, keywords } = props;
  99. if (!text) return null;
  100. if ((Array.isArray(keywords) && keywords.length === 0 || !keywords) && !!text) {
  101. return <div className={className} style={style}>{text}</div>
  102. }
  103. const keywordReg = getKeywordsReg(keywords);
  104. const matchMap = getMatchMap(text, keywordReg);
  105. const splited = text.split(keywordReg);
  106. const content = getLastContent(splited, matchMap, color);
  107. return (
  108. <div className={className} style={style}>
  109. {content}
  110. </div>
  111. );
  112. });
  113. HighLightWord.defaultProps = {
  114. text: '',
  115. keywords: '',
  116. color: DEFAULT_COLOR,
  117. };
  118. export default HighLightWord;

以上可以根据具体情况做些改造, 比如关键词的分隔符, 加些类型判断

参考资料