随着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> ) }; 复制代码