让前端飞深入解读JavaScript

React封装一个复合型Input组件

2018-08-11  本文已影响2人  悟C

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

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

效果展示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;
}
上一篇下一篇

猜你喜欢

热点阅读