继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

React封装一个复合型Input组件

LEATH
关注TA
已关注
手记 454
粉丝 93
获赞 467

在前端开发工作中少不了输入框的身影,但简单的一个输入框已经满足不了设计的需求,往往需要添加点前缀Icon或者后缀Icon,等其他功能的复合型输入框。我们可以看到每个UI库都会为我们提供多功能的Input组件,今天我也实现一个自己的复合型Input组件。

在实现之前我先介绍一个需求:

  • 可以自定义前后缀icon

  • 可在输入框前后添加按钮或者其他自定义组件

  • 当有input有值的时候,有一个快速清除的按钮

  • 回车回调函数

700

效果展示1-1

完整组件代码:

import React, { PureComponent } from 'react';import classNames from 'classnames';import { getOtherProps } from 'utils/tool';import styles from './Input.less';export default class Input extends PureComponent {  constructor(props) {    super(props);    this.state = {      isHasCloseBtn: false,
    };    this.input = React.createRef();
  }

  emptyValue = () => {    this.input.current.value = '';    this.input.current.focus();    this.setState({      isHasCloseBtn: false,
    });
  }
  
  onChange = (e) => {    var value = e.target.value;    if (value) {      if (!this.state.value) {        this.setState({          isHasCloseBtn: true,
        });
      }
    } else {      this.setState({        isHasCloseBtn: false,
      });
    }    if (isFunction(onChange)) {
      onChange(e);
    }
  }

  handleOnPressEnter = (e) => {    const { onPressEnter } = this.props;    if (e.key === 'Enter') {      if (isFunction(onPressEnter)) {
        onPressEnter({          value: e.target.value,
        }, e);
      }
    }
  }

  renderLabeledInput(children) {    const { addonBefore, addonAfter } = this.props;    if (!addonBefore && !addonAfter) {      return children;
    }    const addonAfterGroupWrapperCls = classNames({
      [styles.addonAfterGroupWrapper]: true,
      [styles.isString]: isString(addonAfter),
    });    const addonBeforeGroupWrapper = classNames({
      [styles.addonBeforeGroupWrapper]: true,
      [styles.isString]: isString(addonBefore),
    });    const _addonBefore = addonBefore ? (      <span className={addonBeforeGroupWrapper}>
        {addonBefore}      </span>      
    ) : null;    const _addonAfter = addonAfter ? (      <span className={addonAfterGroupWrapperCls}>
        {addonAfter}      </span>
    ) : null;    return (      <span className={styles.inputWrapper}>
        {_addonBefore}
        {React.cloneElement(children)}
        {_addonAfter}      </span>
    )
    
  }

  renderLabeledIcon = (children) => {    const { prefix, suffix } = this.props;    
    const _prefix = prefix ? (      <span className={styles.prefix} onClick={this.emptyValue}>{prefix}</span>
    ) : null;    const closeBtn = (<i className={styles.closeBtn} onClick={this.emptyValue}></i>)    const _suffix = this.state.isHasCloseBtn ? closeBtn : (      <span className={styles.suffix}>
        {
          suffix ? suffix : null
        }      </span>
    )    return (      <span className={styles.inputGroupWrapper}>
        {_prefix}
        {React.cloneElement(children)}
        {_suffix}      </span>
    );
  }

  renderInput = () => {    const {
      type,
      value,
      size='default',
    } = this.props;    // 这里只对text和password做处理,因为其他type会自带一些功能,像number、date可以基于这个基础input开发
    const _type = type === 'password' ? 'password' : 'text';    // 控制input的尺寸,提高了small、large、default, 具体大小
    const inputCls = classNames({
      [styles.input]: true,
      [styles.small]: (size === 'small'),
      [styles.large]: (size === 'large'),
      [styles.default]: (size === 'default'),
    });    // 定义了getOhterProps方法,用来获取除了第二个参数包含的其他props
    const otherProps = getOtherProps(this.props, ['size', 'addonAfter', 'addonBefore', 'prefix', 'suffix', 'type', 'onPressEnter', 'className', 'onChange']);    
    if ('value' in otherProps) {
      otherProps.value = fixControlledValue(value);
    }    return this.renderLabeledIcon(      <input
        className={inputCls}
        type={_type}
        onChange={this.onChange}
        onKeyPress={this.handleOnPressEnter}
        {...otherProps}        ref={this.input}
      />       
    )
  }

  render() {
    return this.renderLabeledInput(this.renderInput());
  }
}

function isFunction(el) {
  if (getType(el) === "[object Function]") {
    return true;
  }
  return false;
}

function isString(el) {
  if (getType(el) === '[object String]') {
    return true;
  }
  return false;
}

function getType(el) {
  return Object.prototype.toString.call(el);
}

function fixControlledValue(value) {
  if (typeof value === 'undefined' || value === null) {
    return '';
  }
  return value;
}

完整样式index.less

@borderColor: #d9d9d9;@disabledColor: #edeeef;@addonColor: #fafafa;@closeBtnW: 20px;.small {
  height: 40px;
}

.large {
  height: 60px;
}

.default {
  height: 50px;
}

// base css
.inputWrapper {
  position: relative;
  display: table;
  width: 100%;
  .inputGroupWrapper {
    &:not(:first-child) {
      border-top-left-radius: 0;
      border-bottom-left-radius: 0;
    }
    &:not(:last-child) {
      border-top-right-radius: 0;
      border-bottom-right-radius: 0;
    }
  }
}

.inputGroupWrapper {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 100%;
  border: 1px solid @borderColor;
  border-radius: 4px;
  overflow: hidden;
  display: table;
}

.input {
  display: table-cell;
  position: relative;
  border: none;
  width: 100%;
  padding: 6px 12px;
  transition: all .3s;
  outline: none;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  &:disabled {
    background-color: @disabledColor;
    cursor: not-allowed;
  }
  &:not(:first-child) {
    padding-left: 32px;
  }
  &:not(:last-child) {
    padding-right: 32px;
  }
  &::placeholder {
    color: #c3c3c3;
  }
}

.suffix,
.prefix,
.closeBtn {
  position: absolute;
  max-height: 100%;
  overflow: hidden;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  font-size: 12px;
}

.suffix {
  right: 10px;
}
.prefix {
  left: 10px;
}

.closeBtn {
  right: 10px;
  width: @closeBtnW;
  height: @closeBtnW;
  border-radius: 50%;
  color: #fff;
  text-align: center;
  cursor: pointer;
  background-color: #ccc;
  transition: all .3s;
  animation: scale .2s ease-in;
  &::before {
    content: "×";
    display: block;
    position: absolute;
    font-size: 12px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
  }
  &:active {
    background-color: #666;
  }
}@keyframes scale {
  0% {
    opacity: 0;
  }  100% {
    opacity: 1;
  }
}


.addonBeforeGroupWrapper,
.addonAfterGroupWrapper {
  display: table-cell;
  position: relative;
  width: 1px;
  white-space: nowrap;
  vertical-align: middle;
  background-color: @addonColor;
  &.isString {
    padding: 0 6px;
  }
}

.addonBeforeGroupWrapper {
  border-top-left-radius: 4px;
  border-bottom-left-radius: 4px;
  border: 1px solid @borderColor;
  border-right: none;
}

.addonAfterGroupWrapper {
  border-top-right-radius: 4px;
  border-bottom-right-radius: 4px;
  border: 1px solid @borderColor;
  border-left: none;
}



作者:内孤
链接:https://www.jianshu.com/p/60ad4f1af96d


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP