手写一个antd的select组件可多选、查询(更新中)

2,316 阅读2分钟

这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战


写在前面:

  • 个人前端网站:zhangqiang.hk.cn
  • 欢迎加入博主的前端学习qq交流群::706947563专注前端开发,共同学习进步

1 简要概括

本文旨在写一个跟ant design里面Select组件一样的组件,其带有搜索、多选功能;

技术栈:react+typescript+css (除此之外,不依赖其他任何插件比如jquery、或者他人封装好的select组件)。

在写本篇文章时,本组件还正在开发,我会实时更新开发进度,与代码。希望可以把这个做出来吧!

2 思路,来龙去脉

首先我们来看下antd的select

image-20211123161024781

这个既有下拉框,又有搜索,还能多选。

我一开始想的是,用html的select标签来写,然后发现不行;接着想着用input标签,然后发现也不行;再然后试着用html5的

datalist来做,还是不行。

思来想去,看了antd的元素源码,

image-20211123161408084

发现他这个大部分都是div+span

万剑归宗,就用div来写了!! 然后再加上香喷喷的css,隔壁的小孩都馋哭了... 咳!回归正传,目前确定的思路就是div+span+css了,代码已经开动。

3 开始 show code

  • tsx
import React, { useState } from 'react';
// import style from './index.sass'
import {
  OptionsType,
} from './data.d';
import './index.css';
​
const SelectForBase: React.FC<{}> = (props: any) => {
​
  const [isOnFocus, setIsOnFocus] = useState(false);
  const [optionsClickedList, setOptionsClickedList] = useState<OptionsType[]>([
    { label: 'qwer', value: '3' },
    { label: 'err', value: '4' },
  ]); // 当前选中的选项
​
  const options: OptionsType[] = [
    { label: 'sdf', value: '1' },
    { label: 'as', value: '2' },
    { label: 'qwer', value: '3' },
    { label: 'err', value: '4' },
    { label: 'vss', value: '5' },
    { label: 'vscode', value: '6' },
  ];
​
  const onFocus = () => {
    // console.log('获取焦点');
    setIsOnFocus(true);
  }
​
  const onBlur = () => {
    // console.log('失去焦点');
    setIsOnFocus(false);
  }
​
  // 删除选中的option
  const areDeleteOption = (value: OptionsType) => {
    console.log('value:', value);
    let tempArr = optionsClickedList, index = -1;
    for (let i = 0; i < tempArr.length; i++) {
      if (tempArr[i].value === value.value) {
        index = i;
        console.log('index:', index);
        break;
      }
    }
    if (index !== -1) {
      tempArr.splice(index, 1);
      console.log(tempArr);
      setOptionsClickedList([...tempArr]);
    }
  }
​
  return (
    <>
      <div className='select-body'>
        <div className={`select-header ${isOnFocus ? 'select-header-visited' : ''}`} suppressContentEditableWarning contentEditable="true" onFocus={onFocus} onBlur={onBlur}>
          {
            optionsClickedList.map((item: OptionsType, index: number) => {
              return <span key={index} suppressContentEditableWarning contentEditable='false' >
                <span className={`card`} >{item?.label}
                  <span onClick={(e) => { console.log(e); areDeleteOption(item); }} >x</span>
                </span>&nbsp;
              </span>
            })
          }
        </div>
      </div>
    </>
  )
}
​
export default SelectForBase;
  • css
  /* 下拉框整体样式 */
  .select-body {
    box-sizing: border-box;
    position: relative;
    display: inline-block;
    width: 100%;
    height: 60px;
    margin: 0;
    padding: 0;
    color: #000000d9;
    font-size: 14px;
  }
​
  /* 下拉框搜索框 */
  .select-body .select-header {
    width: 100%;
    min-height: 30px;
    position: relative;
    border: 1px solid #dad8d8;
    padding: 5px;
    background-color: #fff;
    transition: all .3s cubic-bezier(.645, .045, .355, 1);
    cursor: text;
  }
​
  .select-body .select-header:hover {
    border: 1px solid rgb(62, 165, 249);
  }
​
  [contenteditable]:focus {
    outline: none;
  }
​
  .select-body .select-header-visited {
    border: 1px solid rgb(62, 165, 249);
    box-shadow: rgba(62, 165, 249, 0.5) 0px 0px 10px;
  }
​
  .select-body .select-header span.card {
    display: inline-block;
    padding: 2px 0px 2px 18px;
    margin: auto 2px;
    border: 1px solid #f0f0f0;
    border-radius: 2px;
    background-color: rgb(245, 245, 245);
    pointer-events: none;
    cursor: default;
  }
​
  .select-body .select-header span.card span {
    display: inline-block;
    margin-left: 2px;
    padding: 0 5px;
    color: rgba(0, 0, 0, 0.5);
    cursor: pointer;
  }
​
  .select-body .select-header span.card span:hover {
    color: rgba(0, 0, 0, 1);
  }
​
​
  /* 下拉框下拉列表 */
  .select-body .select-content {}
​
  • data.d.ts
export type OptionsType = {
  label:string; // 显示的标签值
  value:string; // key值
}

\