不可不知的react模糊搜索与结果高亮

前言

随着vuereact等框架等广泛使用,前端对数据处理的需求越来越多,处理的数据量也越来越大。今天我就接到这么一个需求,为了减少对后端的请求次数,提高性能,前端实现对数据进行轻量的模糊搜索与检索结果的高亮显示。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;
}

如下是进行检索的结果:

不可不知的react模糊搜索与结果高亮_第1张图片

demo 实现说明

使用create-react-app创建项目:

create-react-app --template typescript react-search-web

引入antd做为UI框架,为了与项目中antd版本保持一致,这里我们使用3.26.18版本的antd

yarn add antd@3.26.18

如下是生成的项目目录结构:

不可不知的react模糊搜索与结果高亮_第2张图片

模糊搜索

模糊搜索这里是使用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>
    )
  };

你可能感兴趣的:(React,前端,es6,前端,reactjs,javascript)