全文关键词检索高亮,这个在业务中常有的功能,比如浏览器默认就有个功能,关键词搜索就会匹配你检索的文字,并且会给你高亮,这是怎么实现的呢?
本文是一篇笔者关于replace API
的笔记,希望看完在项目中有所思考和帮助。
正文开始...
在开始正文之前,主要是利用字符串replace
这个API
,你将要了解以下几个知识
1、字符串replace
替换
2、如何扩展elementUI
组件源码支持下拉框关键字搜索高亮
3、正则匹配
对应结果,replace
高阶用法
了解需求
比如,现在一个常用的下拉框,我需要搜索关键词模糊匹配,我们看下代码
<el-form-item label="爱好"> <el-select v-model="condition.fv" clearable filterable placeholder="请选择爱好" > <el-option v-for="(item, index) in favData" :key="index" :label="item" :value="item" > </el-option> </el-select> </el-form-item> <script> export default { data() { return { favData: [ '我喜欢篮球', '我喜欢乒乓球', '足球', '游泳', '跳水', 'aabbccaa', 'hello aa, test', ], } } } </script>
当我在el-select
组件上添加filterable
属性后,就可以关键词过滤了,但是只是过滤了,但是我想关键词高亮
你会发现el-select
显示的label
并没有提供插槽或者其他方式去自定义显示label
,源码里是直接显示的
<!--https://github.com/ElemeFE/element/blob/dev/packages/select/src/option.vue--> <template> <li @mouseenter="hoverItem" @click.stop="selectOptionClick" class="el-select-dropdown__item" v-show="visible" :class="{ 'selected': itemSelected, 'is-disabled': disabled || groupDisabled || limitReached, 'hover': hover }"> <slot> <span>{{ currentLabel }}</span> </slot> </li> </template> <script> export default { name: 'ElOption', computed: { isObject() { return Object.prototype.toString.call(this.value).toLowerCase() === '[object object]'; }, currentLabel() { return this.label || (this.isObject ? '' : this.value); }, } </script>
我们尝试修改扩展增强下这个option
,于是想办法去修改currentLabel
,但是你会发现你想让computed
的currentLabel
返回一个jsx
貌似不太可能,因为渲染出来的会带标签,所以只能考虑重写render
方法
重写Option源码
于是我们重写render
,新建一个extendElement.js
// src/extendElement.js // eslint-disable-next-line import/prefer-default-export export const extendElemenUI = (ElementUI) => { const { Option } = ElementUI; // 重写elementUI下拉框的Option,让其支持模糊搜索关键字高亮 // eslint-disable-next-line no-unused-vars Option.render = function (h) { const { visible, itemSelected, disabled, groupDisabled, limitReached, selectOptionClick, hoverItem, currentLabel, hover, select: { query } } = this; const setSlectClass = () => { let str = 'el-select-dropdown__item'; if (itemSelected) { str += ' selected'; } if (disabled || groupDisabled || limitReached) { str += ' is-disabled'; } if (hover) { str += ' hover'; } return str; }; return (visible ? <li on-mouseenter={hoverItem} on-click={selectOptionClick} class={setSlectClass()} > <slot> <span domPropsInnerHTML={hightText(currentLabel, query, 'all')}></span> </slot> </li > : null); }; };
我们注意到我重写了Option
这个组件,我们在install
安装前就拦截这个组件,然后重写了Option
,主要是在ElementUI
注册前完成,jsx
渲染标签的关键在于domPropsInnerHTML
这个接口,如果在模版中我们就是使用v-html
去代替
import Vue from 'vue'; import 'element-ui/lib/theme-chalk/index.css'; import ElementUI from 'element-ui'; import { installCustComponent } from '@/components'; import { extendElemenUI } from './extendElement'; import App from './App'; import router from './router'; import store from './store'; installCustComponent(); Vue.config.productionTip = false; // 这里进行了扩展,主要是想扩展ElementUI不支持的功能,一定是在组件未注册前进行拦截,重写部分组件 extendElemenUI(ElementUI); Vue.use(ElementUI); /* eslint-disable no-new */ new Vue({ router, store, render: h => h(App), }).$mount('#app');
我们发现在高亮关键字有用到这个hightText
方法,主要支持关键词全匹配与部分匹配,默认全匹配
const hightText = (sourceStr, curentVal, reg = 'all') => { if (curentVal === '') { return sourceStr; } const ret = sourceStr.match(curentVal); const hightStr = Array.isArray(ret) ? ret[0] : ''; if (reg) { // 全匹配 return sourceStr.split(hightStr).reduce((prev, cur) => { if (cur === '') { prev.push(`<span class="hight" style="color: red;font-weight:bold">${hightStr}</span>`); } if (cur) { prev.push(cur); } return prev; }, []).join(''); } return hightStr ? sourceStr.replace( hightStr, `<span class="hight" style="color: red;font-weight:bold">${hightStr}</span>`, ) : `${sourceStr}`; };
在上面的一块代码中我们发现,非全匹配,我们就用到了replace
这个方法,主要是替换匹配到的关键字,但是这个replace
我们结合match
,我们发现无法重复匹配
假设aabbccaa
需要高亮aa
,如果用不借助数组或者正则方式处理,我们使用的是replace
字符串匹配的方式,那么一旦匹配到就结束,所以借助了数组的方式做了一点取巧实现了全检索高亮
看下最终的结果:
replace
replace
高亮关键词基本就已经完成这个需求功能,我们重新看下官方MDNreplace[1]的解释
replace()方法返回一个由替换值(
replacement)替换部分或所有的模式(
pattern)匹配项后的新字符串。模式可以是一个字符串或者一个[正则表达式](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp "正则表达式"),替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。**如果
pattern是字符串,则仅替换第一个匹配项。**
所以我们从这段解释中可以发现,当我们使用replace
替换,如果pattern
是字符串,则仅替换第一个匹配项
var sourceStr = 'aabbbccaa'; const ret = sourceStr.replace('aa', 111); console.log(ret) // 111bbbccaa
但是我们发现匹配模式还可以是正则
所以如果想全匹配,那么可以用正则
来做
var sourceStr = 'aabbbccaa'; const ret = sourceStr.replace(/aa/g, 111); console.log(ret) // 111bbbcc111
所以我们也可以将我们上面的hightText
方法改成下面这样
const hightText = (sourceStr, curentVal, reg = 'all') => { if (curentVal === '') { return sourceStr; } const ret = sourceStr.match(curentVal); const hightStr = Array.isArray(ret) ? ret[0] : ''; const hightDom = text => `<span class="hight" style='color: red;font-weight:bold'>${text}</span>`; if (hightStr) { if (reg) { // 全匹配 return sourceStr.replace(new RegExp(`${hightStr}`, 'ig'), hightDom(hightStr)); } return sourceStr.replace( hightStr, hightDom(hightStr), ); } return sourceStr; };
官方的replace
语法是这样的str.replace(regexp|substr, newSubStr|function)
也就是说replace
的第一个参数是字符串或者正则,第二个参数是字符串或者一个函数
- 字符串
var sourceStr = 'aabbbccaa'; const ret = sourceStr.replace('aa', 111); console.log(ret) // 111bbbccaa
- 正则
var sourceStr = 'aabbbccaa'; const ret = sourceStr.replace(/aa/ig, 111); console.log(ret) // 111bbbcc111
- 函数
const str = 'abc12345#$*%' var newString = str.replace(/([^\d]*)(\d*)([^\w]*)/, function(match, $1, $2, $3, offset, string) { console.log(match, offset, string) return [$1, $2, $3] });
我们看下第二次函数,对应的mactch
与string
是原数据,$1
...$3
是对应正则匹配的,如果我想把中间对应的数字换成其他的呢?
const str = 'abc12345#$*%' var newString = str.replace(/([^\d]*)(\d*)([^\w]*)/, function(match, $1, $2, $3, offset, string) { return $1.replace($1, '公众号:') + $2.replace($2, 'Web技术学苑')+$3.replace($3, '-Maic') }); console.log(newString) //公众号:Web技术学苑-Maic
关于function
的参数可以参考下面这个表
变量名 | 代表的值 |
match |
匹配的子串。(对应于上述的 $&。) |
$1,$2, ... |
假如 replace() 方法的第一个参数是一个RegExp [2] 对象,则代表第 n 个括号匹配的字符串。(对应于上述的 1,1,2 等。)例如,如果是用 /([^\d]*)(\d*)([^\w]*)/ 这个来匹配,$1 就是匹配的 ([^\d]*) ,$2 就是匹配的 (\d*) ,依次类推... |
offset |
匹配到的子字符串在原字符串中的偏移量。(比如,如果原字符串是 'abcd' ,匹配到的子字符串是 'bc' ,那么这个参数将会是 1) |
string |
被匹配的原字符串。 |
在业务中你也会经常看到这样的代码
var sourceStr = 'aabbbccaa'; const ret = sourceStr.replace(/aa/ig, 111).replace('bbb', 222); console.log(ret) // 111222cc111'
replace
调用返回的是一个新字符串,所以可以继续调用replace
方法,因为replace
是挂载在String.prototype
上的方法,所以所有字符串可以链式调用
总结
- 以一个实际例子,通过扩展
el-select
的Option
组件实现高亮模糊关键字匹配与全匹配,不过这种方式有缺陷,无法根据当前组件有条件的选择是否高亮匹配,因为我们是在注册前重写了render
,这样会导致所有下拉组件都会高亮模糊关键字 - 讲解
replace
这个关键字函数,如果字符串替换就要知道这个API
replace
支持正则与字符串匹配,如果是字符串,则只会匹配首次,一旦匹配就成功替换,而正则可以做到全局匹配替换- 关于
replace
第二个参数是回调函数
的几个参数的讲解,当是回调函数时,第一个是match
、string
是原字符串,其余的$1,...$n
是对应正则匹配的内容 - 本文示例code example[3]