不可不知的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;
}

如下是進行檢索的結果:

結果展示

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

如下是生成的項目目錄結構:

目錄結構

模糊搜索

模糊搜索這裏是使用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>
    )
  };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章