react组件库调研之Input篇

1,158 阅读3分钟

背景介绍

距离上次的button调研过去了整整两周,偷懒许久,心生愧疚。

期间碍于金铲铲上大师的大业未成,加上重感冒的侵袭,一直没有时间动笔,今天感冒初愈。决定写一篇input的调研冲冲喜,也可以作为晚上上大师的祭品。

历史文章

  1. react组件库调研之Button篇 juejin.cn/post/702897…
  2. input焦点跳动问题探索 juejin.cn/post/703099…

调研ing

还是参照上一篇的方法,依次从ui对比 功能对比 代码分析三个方面,可能会加上代码风格对比。

调研的库也是老朋友,ant arco semi zent

ui对比

说实话,在inputui上,感觉并没有特别的地方。个人比较是喜欢ant的样式。

image.png

不知道为啥,semi的大哥,要把背景色整成灰色的。

image.png

arco,没有focus时候是灰色,focus以后就变白了。

image.png

不过,众所周知,前端只是设计师的工具人罢了。

功能对比

框架 \ 功能前缀后缀clearablepasswordsearchtextarea自动拓展的多行输入框
ant
semi
arco
zent开发ing××

在功能上,大哥们出奇得统一,只有我们的zent,惜败一筹,只能说,后续尽量补上。

代码风格对比

只有arco使用了hooksemiant还都是class写法。

代码分析

这次就选了clear自动拓展的多行输入框这2个功能来进行分析。本来想选password,但是在上一篇,把password中的功能单拎了出现写了,有兴趣的大哥,可以看看之前的。

clear输入框

arcoant的表现形式都是点击clear图标,重新focus。而semi的表现形式是点击clear后,input失去焦点,在实际场景中,我们大概率不需要失焦,因此,主要分析antarco的代码。

先看antclear图标的代码。

handleReset = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
  this.setValue('', () => {
    this.focus();
  });
  resolveOnChange(this.input, e, this.props.onChange);
};

<CloseCircleFilled
  onClick={handleReset}
  // Do not trigger onBlur when clear input
  // https://github.com/ant-design/ant-design/issues/31200
  onMouseDown={e => e.preventDefault()}
  className={classNames(
    {
      [`${className}-hidden`]: !needClear,
      [`${className}-has-suffix`]: !!suffix,
    },
    className,
  )}
  role="button"
/>

分两种情况,一种是inputfocus状态下,点击clear图标,通过阻止mousedown的默认事件,来阻止失焦,防止重新触发onFocus事件。

另一种情况,在没有focus的状态下,点击clear图标,手动触发focus来使input聚焦。

接下来看看arco的代码。

<IconHover className={`${prefixCls}-clear-icon`}>
  <IconClose
    onClick={(e) => {
      e.stopPropagation();
      if (refInput.current && refInput.current.focus) {
        refInput.current.focus();
      }
      onValueChange && onValueChange('', e);
      onClear && onClear();
    }}
    // keep focus status
    onMouseDown={(e) => {
      e.preventDefault();
    }}
  />
</IconHover>

代码逻辑和ant一致,使用e.preventDefault()阻止失焦。在没有focus的状态下,调用current.focus聚焦。

不过顺便发现了arco中的一个bug

image.png

clear事件绑定在icon上,而外面的一圈iconHover上并没有绑定clear事件。因此在点击iconiconHover中间的一段时,clear事件并没有生效,input没有清空。有兴趣的大哥,可以试一下,然后去提个issue

image.png

自动拓展的多行输入框(内容高度自适应)

看了3个框架的解决方案,基本一致。都是用了github.com/andreypopp/… 的方案。

首先,生成一个mirror-textarea,设置上不可见的style,然后放置到body中。

const HIDDEN_TEXTAREA_STYLE = {
    'min-height': '0',
    'max-height': 'none',
    height: '0',
    visibility: 'hidden',
    overflow: 'hidden',
    position: 'absolute',
    'z-index': '-1000',
    top: '0',
    right: '0',
};

再把会影响高度的style设置进mirror-textarea中。

const SIZING_STYLE = [
    'borderBottomWidth',
    'borderLeftWidth',
    'borderRightWidth',
    'borderTopWidth',
    'boxSizing',
    'fontFamily',
    'fontSize',
    'fontStyle',
    'fontWeight',
    'letterSpacing',
    'lineHeight',
    'paddingBottom',
    'paddingLeft',
    'paddingRight',
    'paddingTop',
    // non-standard
    'tabSize',
    'textIndent',
    // non-standard
    'textRendering',
    'textTransform',
    'width',
];
Object.keys(sizingStyle).forEach(key => {
    hiddenTextarea.style[key] = sizingStyle[key];
});

然后,把value置入mirror-textarea中,因为元素的height0,所以只需读取scrollHeight就是我们所需要的高度。

hiddenTextarea.value = value;
let height = getContentHeight(hiddenTextarea, sizingData);

总结

平心而论,学习input组件的这些日子,收获还是挺大的。

无论是mousedown mouseupfocus之间的联系,还是mirror-textarea的解决方案。都令我有一种茅塞顿开的感觉。

希望在之后的阅读中,能够学到更多。

从写开头到总结,又过去了几天,大师没上,感冒却愈发严重了,祝自己感冒早日康复,争取下一篇select调研篇早日写完。