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