iview下拉select样式_AntDesign4.0的⼀些杂事⼉-Select篇前⼏篇:
Ant Design 4.0 的⼀些杂事⼉ - Table 篇
Ant Design 4.0 的⼀些杂事⼉ - Form 篇
聊完了 Table 和 Form 两个重型组件,我们来继续聊聊看起来不那么重的 Select 组件。它在 Ant Design 4.0 中有哪些变化。如果你读过《Ant Design 4.0 进⾏时》,那你应该已经有了⼤概的印象。当然,即便没有读过也不⽤担⼼。之后会为你细细道来 Select 背后的⼀些故事~
“滚滚的车轮”
如果你关注过底层实现,你会发现 rc-select 和 rc-tree-select 在选择框部分有⼤量相似⽽⼜有⼀点点不同的地⽅。这有⼀段有趣的历史,当年先有的 Select 组件之后结合了 Tree 做了⼀个 TreeSelect 组件。由于这两个组件有少部分代码相同,所以 TreeSelect 复制了⼀部分Select 的代码,继续开发。但是随着时间推移,两者⾃分裂之⽇起,来⾃社区的 issue 以及⼈类的忘性。两者新增的⼀些 API 开始出现了⽭盾。
⼀个典型例⼦就是 inputValue 和 searchValue 之争:
最早,Select 和 TreeSelect 对于搜索框内的⽂字都不⽀持受控模式。之后社区提了 issue 希望对其进⾏控制,于是乎 rc-select 就多了⼀个 inputValue 。
然后⼜过了⼀段时间,社区同学提出 TreeSelect 的搜索框内容在选择⼀个值后会被清空。这在单选没有问题,但是如果是多选的话,就会遇到需要重复输⼊搜索内容的情况。嗯嗯,这的确不好。于是我们就在 3.7.0 版本中添加了 autoClearSearchValue。
那么,既然 TreeSelect 有了。Select 也应该有啊。于是在 3.10.0 ⾥我们给 Select 也添加了 autoClearSearchValue 。
你们发现问题了吗?rc-select 把搜索框的值叫做 inputValue 但是⾃动清除却叫做 autoClearSearchValue。因为两个 API 各⾃发展最后⼜合到了⼀块⼉。
于是,我们在之后的版本进⾏了调整,统⼀使⽤ searchValue ,⽽遗留的 inputValue 在之后的版本会收到 warning 信息。
样式谁为先?
如果你是⼀个设计师,你会发现 v3 版本的 Select 和 TreeSelect 在单选开启搜索的时候样⼦有点不太⼀样:
TreeSelect 搜索框在下⾯
于是我就去做了⼀下考古。发现在 0.12.x 版本中,Select 的输⼊框是在弹窗内部的:
设计。
我们在 issue 中对此有所讨论。从 v3 的设计⾓度来说,下拉框内包含搜索框更容易让⽤户明⽩这个输⼊框是⽤于搜索⽽⾮当前的值。但是在 v4 中,我们经过讨论认为 Select 作为⼀个更加常⽤的组件。⽤户已经培养了使⽤习惯,搜索框位置已经不再是⼀个会让⼈纠结的问题。所以新版设计中,改为 TreeSelect 的搜索框移出于 Select 保持⼀致。
多⽶诺⾻牌
Select 底层的 rc-select 有着⾮常古⽼的历史,追溯到第⼀次提交是 2015 年 1 ⽉。 当年,React 还是 0.12.x 的版本。⼤家对⽣命周期的理解也不像现在拥有⼤量⽂章进⾏介绍。所以⼀些陈年代码⽚段因为⼀直运⾏良好就保留⾄今,其中⼀个困扰⼤家很久的是⼀段不知道为什么直接设置 state 的代码。 @Mack 在编写测试的时候,看到它毅然决然写下了⼀个注释:
对啊,为啥不⽤ `setState`?
@陈帅等13w⼈ 在重构成 Typescript 的时候,看到它,毅然决然的改成了 setState:
事后,我们获得了⼀个 bug:
github/ant-design/ant-design/issues/14262
于是, @陈帅等13w⼈ 默默进⾏了回滚并添加了额外的 comment:
当然,我们在重写代码的时候已经抛开了这些历史包袱。在 v4 版本中,Select 没有这些直接操作 state or DOM or style 的脏代码。转⽽使⽤纯粹的⽣命周期来进⾏管理,以准备迎接未来的 concurrent 模式。
谁先谁后
在⽇常维护⽣活中,我们还收到过⼀个 issue:
github/ant-design/ant-design/issues/17630
第⼀反应估计⼤家都是 onChange 然后清空⼀下 value,但是正如我们上⾯所说的。AutoComplete 实际上是⼀个输⼊框。所以⽤户输⼊的时候是会触发 onChange 的,所以在 AutoComplete 中如果要触发选择应该使⽤ onSelect 事件。 但是,我们的顺序是先 onSelect 后onChange 。这导致,如果⽤户⽤ onSelect 清空了值,⼜会被 onChange 给赋值回来。于是,我们做了调整,onChange 变成了最先触发的。⼩伙伴本终于可以轻松的通过其他事件来修改 value 啦~
原⽣的不⾹吗?
原⽣的 select 组件简单好⽤。不过如果你想要实现⼀些额外的效果,它却⼜不尽如⼈意。我们暂时不提 Option 不能接受 ReactNode 这类问题。先来⼀个简单的例⼦,如果你为原⽣的 select 设置⼀个空值,你会期望页⾯中是⼀个空选项:
<select value="">
<option>one</option>
</select>
⽽实际上却是:
更⿇烦的是,原⽣的 select 由于会默认设置⼀个值到选项⾥。所以如果你的 select 只有⼀个 option 时,你就⽆法触发 onChange 了。为此,⼤部分使⽤原⽣ select 组件为了保留空值不得不提供⼀个补位:
此外,如果你的 select 需要多选,select 会从⼀⾏变成多⾏展⽰。其对应的 onChange 事件获得的 value 也不是你所预期的。
为此,⼏乎每⼀个组件库总是会着⼿解决 Select 组件的问题。 将其进⾏提炼、抽象变得简单、好看且“好⽤”。
value 的同步问题
在我们的⽇常维护中,⼀个关于 value 的常见提问就是:“为什么 value 不在 options 中却可以展⽰。这是⼀个 bug!”
的确,它的⾏为和原⽣的 select 不太⼀样,但是这其实是有意为之。从交互⾓度看,如果你给⼀个组件赋值。那么⽆论是⽤户还是开发者,都会存在⼀个预期。即:
⽤户:我看到了 Select 的值为 a,我不需要打开这个 Select 看⼀圈 options ,我也知道它的值是 a。
开发者:我为这个 Select 设置了值为 a,那么我应该对我赋的值负责。组件不应该在我的预期外修改
这个值。
这是⼀个很有趣的问题,试想⼀下有个 Select ,它的 options 是异步加载的。那么在其 value 加载完成且 options 还没有准备好的时候。你预期 Select 的值应该是什么?很显然,如果是没有值,那么受控的 value 就和展⽰的值不同步了。更有甚者,如果你的 options 是依赖于另⼀个控件来动态加载。那么你的 Select 值就会反复处于 value => null => value 的状态。对于⽤户⽽⾔,它处于即是有值⼜是⽆值的状态。 即便我并不是想设置这个 Select,我只是打开这个页⾯看看配置⽽已。
所以,⽆论 options 是什么状态。Select 的展⽰值与其内部值都应该跟随 value 受控。
combobox 是不是⼀个 Select?
在 v3 初期,我们的 Select 提供了⼀个 combobox 模式,你可以理解为现在的 AutoComplete 组件。combobox 模式下,Select 相当于⼀个 Input 组件,只是它提供了⼀个⾃动完成的下拉选项卡,其输⼊框内的值就是 value。
但是,这也正是让⼈困扰的地⽅。开发者很难分清 combobox 模式到底和其他的模式有什么区别:
于是,我们不得不反复告知 combobox 设计上的区别:
然后收获了
为此,我们最终决定将 combobox 从 Select 中抽离出来。转成⼀个新的组件 AutoComplete。它虽然在内部与 Select 共享了⼤量代码,但是仍然毅然决然的与 Select 分道扬镳以降低开发者的理解成本。
杂谈:
杂谈:我们推荐你可以看⼀下今年 SEE Conf 中 @林外 关于 《Ant Design 4.0:创造快乐⼯作》的演讲。Ant Design 对于组件设计会保持克制,因⽽我们会从设计与实现的⾓度共同去探索⼀个组件所代表的意义。也正是于此,AutoComplete 应该与 Select 进⾏拆分以达到开发者更好的开发体验。减少困扰就是减少⼯作量,减少⼯作量就是早点下班~
选择⽣成器
上⾯我们已经描述过了 TreeSelect 和 Select 在选择框部分⾮常相似,⽽ API 更是⼏乎 70% 都相同。在解决了布局样式统⼀的问题后,我们终于可以将这⼀部分进⾏提炼。
在新版的 rc-select 中,我们抽取了⼀个 generate ⽅法。它主要接收⼀个 OptionList 的⾃定义组件⽤于渲染下拉框部分。这样我们就可以直接复⽤选择框部分的代码,⽽⾃定义 Select 和 TreeSelect 对应的列表或者树形结构了。
键盘的⼩把戏onblur和blur的区别
为了让 Selct 更贴近原⽣组件,我们需要考虑到⼀些键盘交互的问题。它应该有且唯⼀有⼀个能获取焦点的元素来进⾏交互处理,否则就会遇到 Tab 到 Select 时需要多次 Tab 才能切换到下⼀个组件的问题。也会遇到 v3 时期,DatePicker 中 onFocus 和 onBlur 多次触发的问题:
因⽽在设计之初,我们给 Select 预留了⼀个全组件唯⼀的焦点⼊⼝ input。这个 input 在设置 showSe
arch 的时候,是那个搜索框;在未设置的时候,是⼀个 opacity: 0 的幽灵元素;在禁⽤时,它只需要⼀个 disabled 就可以略过键盘交互。 从⽽我们将⼀些组合的键盘操作简化为了对 input 的操作,我们不再需要⾯对多个焦点组件之间进⾏焦点的切换。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论