react组件库调研之Select篇
背景介绍
距离上次的input
调研又差不多过去了整整两周,果然人的惰性是不可战胜的T.T。
历史文章
- react组件库调研之Button篇 juejin.cn/post/702897…
- input焦点跳动问题探索 juejin.cn/post/703099…
- react组件库调研之Input篇 juejin.cn/editor/draf…
调研ing
还是参照之前的样子,依次从ui对比
功能对比
代码分析
三个方面,可能会加上代码风格
对比。
调研的库也是老朋友,ant
arco
semi
zent
。
ui对比
select
的样式,和input
的样式基本保持一直,semi
和arco
还是保持了灰色,ant
和zent
依旧是白色。个人比较喜欢白色,也没啥好说的。
功能对比
框架 \ 功能 | 前缀后缀 | 清除 | 创建条目 | 搜索 | 分组 | tag多选 |
---|---|---|---|---|---|---|
ant | 有 | 有 | 有 | 有 | 有 | 有 |
semi | 有 | 有 | 有 | 有 | 有 | 有 |
arco | 有 | 有 | 有 | 有 | 有 | 有 |
zent | 有 | 有 | 改进中 | 有 | 有 | 有 |
在功能上,基本一致。
代码分析
ant
的select
组件的主要代码都放在另一个库rc-select
里,代码读起来只有2个字,折磨...
只能偷个懒,主要的代码分析就在arco
和semi
里做了。如果后面良心发现(闲的没事),可能再把ant
的代码具体分析一下,真の折磨...
自动滚到到选中option
这个功能其实很简单,为什么要单独拎出来呢。当然是因为zent
里没有啦~
因为ant
和arco
都默认开启了虚拟列表,而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
,让虚拟列表滚动到对应位置就行了。
zent
和semi
在实现虚拟列表时,都使用了react-window
的库,有兴趣的同学可以去看一看。而ant
和arco
则是自己实现了虚拟列表的组件。
创建条目
创建条目这个功能,先不说代码,在功能的实现上,已经有挺大的差异了。在semi
中,value
中传入不存在于options
中的值的时候,是不会主动创建的,也不会出现在value
中。但是大哥们会在值上加上notExist
的标记,而arco
和ant
都会创建一个新的option
并选中。
在创建条目以后,semi
创建的option
是永远存在的,除非传入的options
变更。但在ant
和arco
中,创建条目的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
和其他的保持一致,保留了下来。
总结
由于ant
和arco
的功能表现形式基本一致,我相信写arco
的老大哥一定无懈可击,我就偷个懒了。虽然说其实select
组件的代码量都很多,但是大多数的功能都是偏自定义的,所以能单拎出来的说的功能点并不是很多,加上看代码真的很累,所以就选了2点zent
中没有做到的用来举例,可能写的不是很好,多多包涵。
最后确实有个问题,我觉得使用hooks
编写的组件的可读性真的不高,特别是那种包含了多个hook
作为依赖项的自定义hook
,看得我真的是头皮发麻。有没有大哥知道怎么解决这个问题,或者说可能真的是因为我阅读代码的能力太低了T.T
转载自:https://juejin.cn/post/7041804747841650695