react组件库调研之Select篇

1,024 阅读4分钟

背景介绍

距离上次的input调研又差不多过去了整整两周,果然人的惰性是不可战胜的T.T。

历史文章

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

调研ing

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

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

ui对比

select的样式,和input的样式基本保持一直,semiarco还是保持了灰色,antzent依旧是白色。个人比较喜欢白色,也没啥好说的。

功能对比

框架 \ 功能前缀后缀清除创建条目搜索分组tag多选
ant
semi
arco
zent改进中

在功能上,基本一致。

代码分析

antselect组件的主要代码都放在另一个库rc-select里,代码读起来只有2个字,折磨...

只能偷个懒,主要的代码分析就在arcosemi里做了。如果后面良心发现(闲的没事),可能再把ant的代码具体分析一下,真の折磨...

自动滚到到选中option

这个功能其实很简单,为什么要单独拎出来呢。当然是因为zent里没有啦~

因为antarco都默认开启了虚拟列表,而semi是可以选择是否开启的,所以semi的代码中就包含了2种方法。

updateScrollTop: () => {
    // eslint-disable-next-line max-len
    let destNode = document.querySelector(`#${prefixcls}-${this.selectOptionListID} .${prefixcls}-option-selected`) as HTMLDivElement;
    if (Array.isArray(destNode)) {
        // eslint-disable-next-line prefer-destructuring
        destNode = destNode[0];
    }
    if (destNode) {
        /**
         * Scroll the first selected item into view.
         * The reason why ScrollIntoView is not used here is that it may cause page to move.
         */
        const destParent = destNode.parentNode as HTMLDivElement;
        destParent.scrollTop = destNode.offsetTop -
            destParent.offsetTop -
            (destParent.clientHeight / 2) +
            (destNode.clientHeight / 2);
    }
},

通过class找到选中的第一项,然后通过offset的计算,算出滚动的距离,进行滚动。

另一种就是虚拟列表的滚动方案了,找到需要滚动项的index,让虚拟列表滚动到对应位置就行了。

zentsemi在实现虚拟列表时,都使用了react-window的库,有兴趣的同学可以去看一看。而antarco则是自己实现了虚拟列表的组件。

创建条目

创建条目这个功能,先不说代码,在功能的实现上,已经有挺大的差异了。在semi中,value中传入不存在于options中的值的时候,是不会主动创建的,也不会出现在value中。但是大哥们会在值上加上notExist的标记,而arcoant都会创建一个新的option并选中。

在创建条目以后,semi创建的option是永远存在的,除非传入的options变更。但在antarco中,创建条目的option一旦被取消选中之后,都会自动删除掉,不会存在于最新的options中。

说明在不用业务场景下,我们所需要的组件逻辑也是相应变化的。semi就是把每一次options的更改,都保存在了state中维护,只有当外部传入的options变更才会停止维护。而arco则是通过 flatChildren函数,动态得计算需要渲染的options,比如把需要create的和原有的进行拼接,生成需要渲染的list。至于ant,真没看实现逻辑,已经瞎了...

arco useMemo在依赖项变更的时候重新计算flatChildren
const {
  childrenList,
  optionInfoMap,
  optionValueList,
  optionIndexListForArrowKey,
  hasOptGroup,
  hasComplexLabelInOptions,
} = useMemo(() => {
  return flatChildren(
    { children, options, filterOption },
    {
      prefixCls,
      inputValue,
      userCreatedOptions,
      userCreatingOption: allowCreate ? inputValue : '',
    }
  );
}, [children, options, filterOption, inputValue, userCreatedOptions]);
semi
if (selections.has(option.label)) {
    option._selected = true;
    if (allowCreate) {
        delete option._inputCreateOnly;
    }
}

semi的逻辑中,如果选中项包含了option,就会把原有的标记createOption去掉,使得新创建的option和其他的保持一致,保留了下来。

总结

由于antarco的功能表现形式基本一致,我相信写arco的老大哥一定无懈可击,我就偷个懒了。虽然说其实select组件的代码量都很多,但是大多数的功能都是偏自定义的,所以能单拎出来的说的功能点并不是很多,加上看代码真的很累,所以就选了2点zent中没有做到的用来举例,可能写的不是很好,多多包涵。

最后确实有个问题,我觉得使用hooks编写的组件的可读性真的不高,特别是那种包含了多个hook作为依赖项的自定义hook,看得我真的是头皮发麻。有没有大哥知道怎么解决这个问题,或者说可能真的是因为我阅读代码的能力太低了T.T