随着vue
、react
等框架等广泛使用,前端对数据处理的需求越来越多,处理的数据量也越来越大。今天我就接到这么一个需求,为了减少对后端的请求次数,提高性能,前端实现对数据进行轻量的模糊搜索与检索结果的高亮显示。talk is cheap,show me code
,咱们闲话少说,直接看demo
代码(这里我们使用模糊检索antd
中的icon
图标做demo
演示):
如下是App.tsx
文件内容:
// App.tsx
import React, { useState } from 'react';
import { Icon, Input } from 'antd';
import './App.css';
const icons = [
'step-backward',
'step-forward',
'fast-backward',
'fast-forward',
'shrink',
'arrows-alt',
'down',
'up',
'left',
'right',
'caret-up',
'caret-down',
'caret-left',
'caret-right',
'up-circle',
'down-circle',
'left-circle',
'right-circle',
'up-circle-o',
'down-circle-o',
'right-circle-o',
'left-circle-o',
'double-right',
'double-left',
'forward',
'backward',
'rollback',
'enter',
'retweet',
'swap',
'swap-left',
'swap-right',
'arrow-up',
'arrow-down',
'arrow-left',
'arrow-right',
'play-circle',
'play-circle-o',
'up-square',
'down-square',
'left-square',
'right-square',
'up-square-o',
'down-square-o',
'left-square-o',
'right-square-o',
'login',
'logout',
'menu-fold',
'menu-unfold',
'question',
'question-circle-o',
'question-circle',
'plus',
'plus-circle-o',
'plus-circle',
'pause',
'pause-circle-o',
'pause-circle',
'minus',
'minus-circle-o',
'minus-circle',
'plus-square',
'plus-square-o',
'minus-square',
'minus-square-o',
'info',
'info-circle-o',
'info-circle',
'exclamation',
'exclamation-circle-o',
'exclamation-circle',
'close',
'close-circle',
'close-circle-o',
'close-square',
'close-square-o',
'check',
'check-circle',
'check-circle-o',
'check-square',
'check-square-o',
'clock-circle-o',
'clock-circle',
'warning',
'lock',
'unlock',
'area-chart',
'pie-chart',
'bar-chart',
'dot-chart',
'bars',
'book',
'calendar',
'cloud',
'cloud-download',
'code',
'code-o',
'copy',
'credit-card',
'delete',
'desktop',
'download',
'edit',
'ellipsis',
'file',
'file-text',
'file-unknown',
'file-pdf',
'file-word',
'file-excel',
'file-jpg',
'file-ppt',
'file-markdown',
'file-add',
'folder',
'folder-open',
'folder-add',
'hdd',
'frown',
'frown-o',
'meh',
'meh-o',
'smile',
'smile-o',
'inbox',
'laptop',
'appstore-o',
'appstore',
'line-chart',
'link',
'mail',
'mobile',
'notification',
'paper-clip',
'picture',
'poweroff',
'reload',
'search',
'setting',
'share-alt',
'shopping-cart',
'tablet',
'tag',
'tag-o',
'tags',
'tags-o',
'to-top',
'upload',
'user',
'video-camera',
'home',
'loading',
'loading-3-quarters',
'cloud-upload-o',
'cloud-download-o',
'cloud-upload',
'cloud-o',
'star-o',
'star',
'heart-o',
'heart',
'environment',
'environment-o',
'eye',
'eye-o',
'camera',
'camera-o',
'save',
'team',
'solution',
'phone',
'filter',
'exception',
'export',
'customer-service',
'qrcode',
'scan',
'like',
'like-o',
'dislike',
'dislike-o',
'message',
'pay-circle',
'pay-circle-o',
'calculator',
'pushpin',
'pushpin-o',
'bulb',
'select',
'switcher',
'rocket',
'bell',
'disconnect',
'database',
'compass',
'barcode',
'hourglass',
'key',
'flag',
'layout',
'printer',
'sound',
'usb',
'skin',
'tool',
'sync',
'wifi',
'car',
'schedule',
'user-add',
'user-delete',
'usergroup-add',
'usergroup-delete',
'man',
'woman',
'shop',
'gift',
'idcard',
'medicine-box',
'red-envelope',
'coffee',
'copyright',
'trademark',
'safety',
'wallet',
'bank',
'trophy',
'contacts',
'global',
'shake',
'api',
'fork',
'dashboard',
'form',
'table',
'profile',
'android',
'android-o',
'apple',
'apple-o',
'windows',
'windows-o',
'ie',
'chrome',
'github',
'aliwangwang',
'aliwangwang-o',
'dingding',
'dingding-o',
'weibo-square',
'weibo-circle',
'taobao-circle',
'html5',
'weibo',
'twitter',
'wechat',
'youtube',
'alipay-circle',
'taobao',
'skype',
'qq',
'medium-workmark',
'gitlab',
'medium',
'linkedin',
'google-plus',
'dropbox',
'facebook',
'codepen',
'amazon',
'google',
'codepen-circle',
'alipay',
'ant-design',
'aliyun',
'zhihu',
'slack',
'slack-square',
'behance',
'behance-square',
'dribbble',
'dribbble-square',
'instagram',
'yuque',
];
function App() {
const [iconList, setIconList] = useState(icons);
const [keyWord, setKeyWord] = useState('');
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const search = e.target.value;
setIconList(
search ? (
icons.filter(type => type.includes(search))
) : icons
);
setKeyWord(search);
};
const renderIconType = (iconType: string, search: string) => {
if (!search) return iconType;
const reg = new RegExp(search, 'ig');
const splitIconTypes = iconType.split(reg);
const matchIconTypes = iconType.match(reg) as Array<string>;
return (
<React.Fragment>
{
matchIconTypes.map((_, index) => {
return (
<React.Fragment key={ index }>
<span>{ splitIconTypes[index] }</span>
<span className="icon-mark">{ matchIconTypes[index] }</span>
</React.Fragment>
)
})
}
<span>
{ splitIconTypes[splitIconTypes.length - 1] || '' }
</span>
</React.Fragment>
)
};
return (
<div className="App">
<header className="App-header">
<Input.Search placeholder="请输入" addonBefore="请输入关键字" value={ keyWord } onChange={ handleSearch } allowClear />
</header>
<div>
<ul className="icon-list">
{
iconList.map((iconType) => {
return (
<li className="icon-item" key={ iconType }>
<Icon className="icon" type={ iconType } />
<p>{ renderIconType(iconType, keyWord) }</p>
</li>
)
})
}
</ul>
</div>
</div>
);
}
export default App;
如下是App.css
文件内容:
// App.css
@import '~antd/dist/antd.css';
.App {
padding: 15px;
}
.App-header {
width: 30%;
margin: 0 auto;
}
.icon-list {
list-style: none;
margin: 10px 0;
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-column-gap: 10px;
align-items: center;
justify-content: center;
}
.icon-item {
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
margin: 10px;
}
.icon > svg {
width: 30px;
height: 30px;
}
.icon-item p {
margin-top: 10px;
text-align: center;
}
.icon-mark {
color: #2db7f5;
}
如下是进行检索的结果:
使用create-react-app
创建项目:
create-react-app --template typescript react-search-web
引入antd
做为UI
框架,为了与项目中antd
版本保持一致,这里我们使用3.26.18
版本的antd
:
yarn add antd@3.26.18
如下是生成的项目目录结构:
模糊搜索这里是使用includes
方法实现,检索现有icons
数据中包含search
值的内容,如下代码所示:
icons.filter(type => type.includes(search))
对于检索结果进行高亮显示,主要是通过search
值构建正则对iconType
进行分割,将与search
值一致的内容进行color: #2db7f5
高亮,代码如下:
const renderIconType = (iconType: string, search: string) => {
if (!search) return iconType;
const reg = new RegExp(search, 'ig');
const splitIconTypes = iconType.split(reg);
const matchIconTypes = iconType.match(reg) as Array<string>;
return (
<React.Fragment>
{
matchIconTypes.map((_, index) => {
return (
<React.Fragment key={ index }>
<span>{ splitIconTypes[index] }</span>
<span className="icon-mark">{ matchIconTypes[index] }</span>
</React.Fragment>
)
})
}
<span>
{ splitIconTypes[splitIconTypes.length - 1] || '' }
</span>
</React.Fragment>
)
};