React16.13 简单封装一个select组件

React16.13 简单封装一个select组件

GInput.css

html, body {
  height: 100%; padding: 0; margin: 0;
}
/*GInput*/
.g-input-box > .g-input {
  border: 1px solid #666;
}
.g-input-box.error > .g-input {
  border: 1px solid #f00;
}
.g-input-box.error > .error-msg, .g-input-box.error > .g-input {
  color: #f00;
}
/*GSelect*/
.hidden {
  display: none;
}
.g-input {
  display: block; box-sizing: border-box; width: 100%;
}
.select-outer {
  border: 1px solid #666;
}
.select-box, .select-outer {
  position: relative; display: inline-block;cursor: pointer; width: 100%;
}
.select-box > .g-input-box > .g-input {
  cursor: pointer; border: 0; line-height: 22px; height: 22px; padding: 0; margin: 0;
}
.select-box > .g-input-box{
  padding-right: 22px;
}
.sel-down-arrow {
  width: 18px; height: 18px; position: absolute; right: 0; top: 50%; margin-top: -9px;
  background-color: brown;
}
/* 禁止选中文本,双击arrow的时候,有时候会把下拉框中的文本选中,禁止arrow文本选中,可以避免这种发生这种情况 */
.sel-down-arrow {
  -moz-user-select:none; /*火狐*/
  -webkit-user-select:none; /*webkit浏览器*/
  -ms-user-select:none; /*IE10*/
  -khtml-user-select:none; /*早期浏览器*/
  user-select:none;
}
.down-list-box {
  position: absolute;left: -1px; width: 100%; z-index: 999;
  background-color: #fff; border: 1px solid #666;
}
.down-list-box > p {
  line-height: 32px; padding: 0; text-align: center; margin: 0;
}
.data-ul {
  line-height: 30px; max-height: 150px; overflow-y: auto;
}
.data-ul > li:hover, .data-ul > li.checked {
  background-color: rgb(211, 208, 208);
}
.select-outer.error {
  border: 1px solid #f00;
}
.select-outer.error .g-input {
  color: #f00;
}
.select-outer > .error-msg {
  position: absolute; top: 100%; left: 0; width: 100%; line-height: 20px; color: #f00; font-size: small; z-index: 9;
}

GSelect.jsx

import React, { Component } from 'react';

import './GInput.css'

// props 参数:
// --props 
// ------可选:source:Array [{val,text,checked:boolean}] | noDataDes className
// ------可选:onChange事件,通过事件回调获取select选择的值
// ------可选:rule:{ regx:RegExp | function, [callback: function, errorMsg: '', requiredRuleInit: boolean] } 验证规则
//------------------regx function返回true,或者正则test()返回true,则表示当前值符合规则,如果为false,则表示当前值有错误
//------------------requiredRuleInit 初始化数据的时候是否校验,默认true校验
class GSelect extends Component {
  constructor(props) {
    super();
    if (!window.GEelementIndex) {
      window.GEelementIndex = 1
    }
    let selectInitData = this.getInitData(props)
    let errMsg = ''
    let hasErrorFirst = false
    if (props.rule) {
      errMsg = props.rule.errorMsg || ''
      if (!(typeof props.rule.requiredRuleInit === 'boolean' && !props.rule.requiredRuleInit)) {
        // requiredRuleInit: 非false 进行校验
        hasErrorFirst = this.regxRule(props, selectInitData.value)
      }
    }
    this.state = {
      GEelementIndex: window.GEelementIndex++,
      source: props.source || [],
      noDataDes: props.noDataDes || '暂无数据',
      isDownHidden: true,
      onChange: props.onChange || null,
      valueText: selectInitData.valueText,
      value: selectInitData.value,
      checkIndex: selectInitData.checkIndex,
      stylePosDownListBox: {
        bottom: ''
      },
      hasError: hasErrorFirst,
      errorMsg: errMsg
    }
    this.handleSelectDown = this.handleSelectDownFn.bind(this)
    this.handleDropSelectClick = this.handleDropSelectClickFn.bind(this)
  }
  UNSAFE_componentWillMount () {}
  componentDidMount () {
    if(!window.cancelSelectPopFn) {
      window.cancelSelectPopFn = (event) => {
        let $target = event.target
        let classStr = $target.className
        if (classStr.indexOf('sel-down-arrow') > -1 || classStr.indexOf('select-box') > -1) {
          return false
        }
        else if (classStr.indexOf('g-input') > -1 || classStr.indexOf('g-input-box') > -1) {
          if($target.getAttribute('data-select')||($target.firstChild && $target.firstChild.getAttribute('data-select'))) {
            return false
          }
        }
        console.log('触发了GSelect监听的body点击事件。')
        this.closeDropDown()
      }
      // false是冒泡监听事件,true是捕获监听
      document.body.addEventListener('click', window.cancelSelectPopFn, false);
    }
  }
  closeDropDown ($downListBoxSelf) {
    let selectDownWrap = document.querySelectorAll('.down-list-box:not(.hidden)')
    if(selectDownWrap && selectDownWrap.length > 0) {
      for (let i = 0; i < selectDownWrap.length; i++) {
        if (selectDownWrap[i].className.indexOf('hidden') < 0 && selectDownWrap[i] !== $downListBoxSelf) {
          selectDownWrap[i].classList.add('hidden')
          // todo
          // 这种情况也应该初始化下拉框位置的,但是不能使用this太麻烦,暂时不处理
          // this.initDownBoxPos()
        }
      }
    }
  }
  handleSelectDownFn (event) {
    console.log('触发了Gselect点击事件。')
    event = event || window.event;
    let dropDownUl = document.querySelector(`#select-${this.state.GEelementIndex}>.down-list-box`);
    // 隐藏其他打开的下拉框
    this.closeDropDown(dropDownUl)
    //
    if (dropDownUl && dropDownUl.className.indexOf('hidden') > -1) {
      // 下拉框隐藏, 切换成显示
      if (event) {
        // 展示下拉框
        if (this.state.isDownHidden) {
          this.setState({
            isDownHidden: false
          })
        } else {
          // isDownHidden已经是false,但是下拉框却显示了,说明是body点击事件触发的 dropDownUl hidden
          dropDownUl.classList.remove('hidden')
        }
        // 计算下拉框位置
        let selectHeight = document.getElementById(`select-${this.state.GEelementIndex}`).clientHeight
        let sizeObj = {
          selectHeight:  selectHeight,
          topDistanceY: event.clientY - selectHeight,
          downDistanceY: window.innerHeight - event.clientY - selectHeight
        }
        let timer = setTimeout(() => {
          clearTimeout(timer)
          timer = null
          this.caculateDownBoxPos(sizeObj, dropDownUl)
        }, 0)
      }
      
    } else {
      // 下拉框显示, 切换成隐藏
      this.setState({
        isDownHidden: true
      })
      this.initDownBoxPos()
    }
    event.preventDefault();
    event.stopPropagation();
  }
  renderDownList () {
    return this.state.source.map((item, i) => {
      return (
        
  • {item.text}
  • ) }) } getInitData(props) { let obj = {} let arr = props.source if(Array.isArray(arr)) { if (arr.length > 0) { let check = 0 let hasCheck = false for (let i = 0; i < arr.length; i++) { if (arr[i].val && arr[i].text && arr['checked']) { check = i hasCheck = true break } } obj = { valueText: arr[check].text || '', value: arr[check].val || '', checkIndex: hasCheck ? check : -1 } } } else { console.error('GSelect source data is Array, yours wrong.') } return obj } handleDropSelectClickFn (event) { event = event || window.event let tar = event.target if (tar.className.indexOf('checked') > -1) { return false } let obj = { valueText: tar.textContent || '', value: tar.getAttribute('data-val') || '', checkIndex: tar.getAttribute('data-i') } this.setState({ valueText: obj.valueText, value: obj.value, checkIndex: obj.checkIndex }) if(typeof this.state.onChange === 'function') { this.state.onChange(obj) } } initDownBoxPos () { if (this.state.stylePosDownListBox.bottom) { this.setState({ stylePosDownListBox: { bottom: '' } }) } } caculateDownBoxPos (sizeObj, dropDownBox) { dropDownBox = dropDownBox ? dropDownBox : document.querySelector(`#select-${this.state.GEelementIndex}>.down-list-box`); sizeObj.dropDownHeight = dropDownBox.clientHeight if (sizeObj.downDistanceY >= sizeObj.dropDownHeight) { return false } else if (sizeObj.topDistanceY >= sizeObj.dropDownHeight) { this.setState({ stylePosDownListBox: { bottom: sizeObj.selectHeight + 'px' } }) } } hasRule (props) { let flag = '' if (props.rule) { if (props.rule.regx instanceof RegExp) { flag = 'RegExp' } else if (typeof props.rule.regx === 'function') { flag = 'function' } } return flag } regxRule (props, expValue) { expValue = expValue || '' let error = false let ruleFlag = this.hasRule(props) if (ruleFlag) { let flag; if(ruleFlag === 'RegExp') { flag = new RegExp(props.rule.regx).test(expValue) } else if (ruleFlag === 'function') { flag = props.rule.regx(expValue) } if (!flag) { error = true } if (typeof props.rule.callback === 'function') { props.rule.callback(flag) } } return error } render () { return (
    { this.state.source.length > 0 ? (
      { this.renderDownList() }
    ) : (

    {this.state.noDataDes}

    ) }
    { this.state.hasError && (
    {this.state.errorMsg}
    ) }
    ) } } export default GSelect;

    UseSelectPage.jsx

    import React, { Component } from "react";
    import GSelect from './components/form/GSelect.jsx'
    
    import '../static/main.css'
    import GInput from "./components/form/GInput.jsx";
    
    class UseSelectPage extends Component {
      constructor(props) {
        super();
        this.state={
          form: {
            listArr: [
              {val: 'abc1', text: 'abc输入法1'},
              {val: 'abc2', text: 'abc输入法2'},
              {val: 'abc3', text: 'abc输入法3'},
              {val: 'abc4', text: 'abc输入法4'},
              {val: 'abc5', text: 'abc输入法5'},
              {val: 'abc6', text: 'abc输入法6'},
              {val: 'abc7', text: 'abc输入法7'},
              {val: 'abc8', text: 'abc输入法8'}
            ]
          },
          rules: {
            selectRule: {
              regx: function (val) {
                if (val !== null && val !== '' && val !== undefined) {
                  return true
                } else {
                  return false
                }
              },
              callback: function (flag) {
                console.log('pwdRule:', flag)
              },
              errorMsg: '该项为必选项'
            }
          }
        }
        this.handleInput123ChangeFn = this.handleInput123Change.bind(this)
      }
      UNSAFE_componentWillMount () {}
      componentDidMount () {}
      handleInput123Change (val) {
        console.log('handleInput123Change:', val)
      }
      render () {
        return (
          

    ctx page

    ) } } export default UseSelectPage;

    效果图:


    image.png

    你可能感兴趣的:(React16.13 简单封装一个select组件)