Cascader Select

层级搜索

在 ant-design3.0 的 select component 基础上,用 dropdownRender 自定义搜索选项

const ellipsePath: taxonomyType[] = useMemo(() => {
  let ePath = clone(path)
  const len = path.length
  if (path.length > 2) {
    ePath = [path[len - 2], path[len - 1]]
    ePath.unshift({
      id: 'home',
      name: '...',
    })
  }
  return ePath
}, [path])

return (
  <Select
    mode={mode}
    value={checkedList}
    onChange={handleChange}
    dropdownRender={(menu) => (
      <div>
        {ellipsePath?.length ? (
          <div className={styles.pathWrapper}>
            <span key='home' onMouseDown={(e) => e.preventDefault()} onClick={() => prevLevel('home')}>
              <img src={homeImg} alt='home' />
            </span>
            {ellipsePath.map((p) => (
              <span key={p.id} onMouseDown={(e) => e.preventDefault()} onClick={() => prevLevel(p.id)}>
                {`${p.name} / `}
              </span>
            ))}
          </div>
        ) : null}
        {optionList.length ? (
          <ul>
            {optionList.map((op, index) => (
              <li>
                {op.name}
                {op.hasCascader ? <span onClick={() => nextLevel(op.id, op.name)} /> : null}
              </li>
            ))}
          </ul>
        ) : null}
      </div>
    )}
  >
    {optionList}
  </Select>
)

模拟搜索框

隐藏 ant-design 自带的搜索框,将原搜索框中的值填入模拟搜索框

const [searchValue, setSearch] = useState('')
const [lastSearch, setLastSearch] = useState('')

const filterOption = (inputValue: string) => {
  const originSearch = document.querySelector(`#${popupContainer} .ant-select-search__field`)
  if (originSearch && !originSearch?.getAttribute('spellcheck')) {
    originSearch.setAttribute('spellcheck', 'false')
    originSearch.setAttribute('unselectable', 'on')
  }
  if (inputValue.length < lastSearch.length) {
    setLastSearch(inputValue)
  } else {
    setSearch(inputValue.slice(lastSearch.length))
  }
}
const clearSearch = (needRefreshHierarchy = true) => {
  const originSearch = document.querySelector(`#${popupContainer} .ant-select-search__field`)
  if (originSearch?.getAttribute('value')) {
    setLastSearch(originSearch?.getAttribute('value') || '')
  }
  setSearch('')
}

return (
  <Select
    onBlur={() => clearSearch(true)}
    showSearch
    onSearch={filterOption}
    autoClearSearchValue
    dropdownRender={(menu) => (
      <div>
        <Search
          placeholder='input search text'
          value={searchValue}
          allowClear
          onChange={clearSearch}
          onChange={() => clearSearch(true)}
        />
        {optionList}
      </div>
    )}
  >
    {optionList}
  </Select>
)

滚动加载

当 optionlist 中的选项超过 3000 个,用户点击选择框会有明显的延时

1. onscroll

先加载 10 个选项,用 onScroll 事件监听用户滚动到列表底部后,加载后 10 个选项

const handleOptionScroll = (e: any) => {
  e.persist()
  const { scrollTop, offsetHeight, scrollHeight } = e.target
  const current = optionList.length
  if (scrollTop + offsetHeight >= scrollHeight && current < options.length) {
    setOptionList(options.slice(0, current + 10))
  }
}

return (
  <Select
    dropdownRender={(menu) => (
      <ul id='optionlist' className='ant-select-dropdown-menu' onScroll={handleOptionScroll}>
        {optionList.map((op, index) => (
          <li>{op.name}</li>
        ))}
      </ul>
    )}
  >
    {optionList}
  </Select>
)

缺点:

  • 进度条会慢慢变长,所以用户无法直接拖动进度条到列表底部
  • 滚动加载会有一点点卡壳

2. react-virtualized 只渲染可视框内的选项

npm i --save @types/react-virtualized

Required Property:

Property Type Required Description
height number y list height
width number y list width
rowCount number y list length
rowHeight number or func y row height
rowRenderer func y item content
overscanRowCount number n 可视区域前后预渲染行数,默认10
import { AutoSizer, List } from 'react-virtualized'

return (
  <Select
    dropdownRender={(menu) => (
      <AutoSizer>
        {({ width }) => (
          <List
            height={250}
            width={width}
            rowCount={optionlist.length}
            rowHeight={20}
            rowRenderer={({ key, index }) => <li key={key}>{op.name}</li>}
          />
        )}
      </AutoSizer>
    )}
  >
    {optionList}
  </Select>
)

缺点:

  • width为必须参数,且只能传数字不’100%‘,需要撑满整行时需要包一层AutoSizer
  • rowHeight为必须参数,需要自适应高度时需要用list.get(index % list.size)计算
  • 滑动快时列表下方有空白,渲染慢

https://www.npmjs.com/package/react-virtualized

https://github.com/bvaughn/react-virtualized/blob/master/docs/List.md

3. react-virtuoso 只渲染可视框内的选项

npm install react-virtuoso

Property:

Property Type Required Description
data readonly D[] n data list
itemContent ItemContent n Set the callback to specify the contents of the item.
totalCount number n If data is set, the total count will be inferred from the length of the array.
import { Virtuoso } from 'react-virtuoso'

return (
  <Select
    dropdownRender={(menu) => (
      <Virtuoso
        data={optionList}
        style={{ height: '250px' }}
        itemContent={(index, op) => <li>{op.name}</li>}
        onMouseDown={e => e.preventDefault()} // 否则无法拖动滚动条
      />
    )}
  >
    {optionList}
  </Select>
)

优点:

  • 没有required property, 最少传个data和itemContent就能看到效果
  • 不需要width和rowHeight, 自适应能力强
  • 滚动相当流畅

https://virtuoso.dev/

https://virtuoso.dev/virtuoso-api-reference/

Article
Tagcloud
DVA Java Express Architecture Azure CI/CD database ML AWS ETL nest sql AntV Next Deep Learning Flutter TypeScript Angular DevTools Microsoft egg Tableau SAP Token Regexp Unit test Nginx nodeJS sails wechat Jmeter HTML2Canvas Swift Jenkins JS event GTM Algorithm Echarts React-Admin Rest React hook Flux Redux ES6 Route Component Ref AJAX Form JSX Virtual Dom Javascript CSS design pattern