antd 2X 自定义 select 下拉菜单

3,116 阅读2分钟

原因:由于使用了一些比较老的react组件,导致现有的antd 无法升级到3X、4X, 并且最近产品提的需求比较骚操作。 这种select.Option中有按钮的

撸起袖子开始干

1、现引入要使用的其他组件和工具包 (主要需要三个元素 输入框、图标、下拉菜单)

import * as React from 'react'

import { Menu, Dropdown, Input, Icon } from 'antd'
import * as _ from 'lodash'

2、仿照现有的select,export两个东东

export class NewOption extends React.Component<IOptionPorps, IState> {
  // 中间层组件div来组织react合成事件冒泡
  click = e => {
    // react合成事件数量大于当前节点现有的事件数量即 menuItem 和div两个表示子组件可能存在合成事件
    const hasChildEvent = e._dispatchListeners.length > 2
    if (hasChildEvent) {
      e.stopPropagation()
      return false
    }
  }
  render() {
    const { children, value, key } = this.props
    return (
      <Menu.Item {...this.props} eventKey={key ? key + '' : value + ''}>
        <div className="option-item" onClick={this.click}>
          {children}
        </div>
      </Menu.Item>
    )
  }
}

export default class NewSelect extends React.Component<IProps, IState> {
  state: IState = {
    inputValue: undefined,
    showOption: false,
    placeholder: undefined
  }
  public clear = () => {
    this.setState({
      inputValue: undefined,
      placeholder: undefined
    })
    this.body.focus()
  }
  body = undefined
  input = undefined
  // option选中时间
  onSelect = ({ item, key }) => {
    const value = this.findChildContent(item)
    this.setState(
      {
        inputValue: value,
        placeholder: value,
        showOption: false
      },
      () => {
        const { onSelect } = this.props
        if (!!onSelect) {
          onSelect(key)
        }
        this.inputBlur()
      }
    )
  }
  // 找到子元素内容
  findChildContent = item => {
    if (item.props.children) {
      const next = _.isArray(item.props.children)
        ? item.props.children[0]
        : item.props.children
      if (_.isString(next)) {
        return next
      } else {
        return this.findChildContent(next)
      }
    } else {
      return undefined
    }
  }

  inputBlur = () => {
    this.input.blur()
    this.setState({
      showOption: false,
      inputValue: this.state.placeholder
    })
  }
  // 输入框输入事件
  onChangeInput = _.debounce(value => {
    const { onSearch } = this.props
    if (!!onSearch) {
      onSearch(value)
    }
  }, 500)

  // mousedown listener函数
  mousedown = e => {
    event.preventDefault()
    this.input.focus()
    this.setState({
      showOption: true,
      inputValue: undefined
    })
  }
  componentDidMount() {
    this.body.addEventListener('mousedown', this.mousedown)
  }
  componentDidUpdate() {
    if (this.state.showOption && this.input) {
      this.input.focus()
    }
  }
  componentWillUnmount() {
    this.body.removeEventListener('mousedown', this.mousedown)
  }
  // 需要三个元素 输入框、图标、下拉菜单
  public render() {
    const { showOption } = this.state
    const { children, loading } = this.props
    const width = this.props.width || '180px'
    const menu = (
      <Menu style={{ width }} onClick={this.onSelect}>
        {children}
      </Menu>
    )
    const downIcon = <Icon type="down" className="icon" />
    const upIcon = <Icon type="up" className="icon" />
    const loadingIcon = <Icon type="loading" className="icon" />
    return (
      <Dropdown
        overlay={menu}
        visible={showOption}
        getPopupContainer={() => this.body}
      >
        <div
          className="customize-select"
          style={{ width }}
          ref={ref => (this.body = ref)}
        >
          <Input
            disabled={loading}
            onChange={e => {
              this.setState({ inputValue: e.target.value })
              this.onChangeInput(e.target.value)
            }}
            value={this.state.inputValue}
            ref={ref => (this.input = ref)}
            style={{ width: '100%' }}
            placeholder={this.state.placeholder}
            onBlur={this.inputBlur}
          />
          {showOption && !loading && downIcon}
          {!showOption && !loading && upIcon}
          {loading && loadingIcon}
        </div>
      </Dropdown>
    )
  }
}

忘记贴出interface 了

interface IProps {
  width?: string | number
  value?: string
  loading: boolean
  onSelect?: (params) => void
  onSearch?: (params) => void
}

interface IState {
  inputValue: string
  showOption: boolean
  placeholder: string
}
interface IOptionPorps {
  value: string
  key?: string
  disabled?: boolean
  selectedKeys?: string[]
  eventKey?: string
}

备注:

如果其中的阻止事件的中间层不理解,或者mousedown事件也不理解,那么请去看看react合成事件原理,即onClick等事件到底是怎么执行的

style.less 不要忘记欧!

.customize-select {
  position: relative;
  display: inline-block;
  .icon {
    position: absolute;
    right: 12px;
    top: calc(50% - 6px);
  }

  .option-item {
    width: 100%;
  }
}

接下来看看怎么用吧:

    <NewSelect
        width="180px"
        loading={loading}
        onSearch={this.onSearch}
        onSelect={this.onSelect}
        ref={ref => (this.select = ref)}
    >
              
        <NewOption
            value={'1'}
            key={'1'}
            disabled={this.state.disabled}
        >
            <div className="item">
                <span>我是label</span>
                <span>
                    <Button
                        size="small"
                        onClick={this.onClick}
                    >
                        点击
                    </Button>
                </span>
            </div>
    </ NewOption>

好了,可以用了,好开心,关于失焦和聚焦可以自行更改来达到自己使用的目的。