您的位置:首页 > Web前端 > React

下拉选择组件从内部组件到 ant-design 不得不说的三个故事

2017-12-09 11:58 4003 查看
前言

昨天接到了第一个需求吧,目标就是一句话:把 @mtfe/react-dropdown 组件替换为 antd 的Select组件

用图来说就是从







分解一下工作点

表现当长度过长时,不会出现滚动条,而是向左或向右扩展

正常的过滤以及选择功能

具体实现:

要求 组件自由配置输入框与下拉框宽度样式 灵活配置下拉框相对位置

输入字符过滤,键盘上下键和回车可以在下拉框中控制选择内容

明确之后就可以具体开工了

步骤

一:熟悉旧代码

事情也比较简单,暂时需求只是替换页面一个组件,所以大致浏览一下页面代码,主要集中注意力到旧组件的逻辑

当然可能这里更难的是进行咱们美团内部 git 初始化,总之 clone 下仓库之后还是比较顺利的

二:熟悉ant-design

以前用过一些ant-design,所以简单熟悉一下各种 API,Select 中文文档还是说的比较直观的,说实话各种语法糖还是比较多的,这也是 ant 除了外观设计不错之外值得称道的点,不过过度设计和功能简洁实用之间度其实才是最难的点吧,虽然个人不太懂,但据说 ant 还是比较吊的,另外最近好像开始 SSE conf 报名了,感觉大神云集,想去看看啊,咳咳跑题了,总之设计要在 易用强大和过度设计之间取舍还是比较难的,希望自己有机会可以学习一下。

当阅读文档时还是比较顺利的,第二条(正常的过滤以及选择功能)ant 原生提供,所以这就完全不必费心,不得不再赞一个,功能确实比较强大,整体设计也简单、人性化



至于第一条,好像也可以很简单的解决了,直接用第二三个属性配置一下就就好了。ok,文章结束了

显然这个题目表示这篇文章尚未结束,第三个属性坑爹的失效了。

三:有一点问题



antd 的 dropdown 组件是相对页面绝对定位,并且是 JS 动态设置位置的,所以首先你得想办法把它的动态 left 属性干掉,否则你的 right: 0永远都不可能生效。

所以让我们看看 antd Select 里面是怎么实现的,我们的 dropdownStyle 属性最后在哪里生效

首先相同的是,为了扩展性,你传入的 props 一般来说都会被传到最底层的组件上

const {
prefixCls,
className = '',
size,
mode,
...restProps,
} = this.props;

<RcSelect
{...restProps}
// 其他略
/>


寻根溯源,是 react-component 中 Select 仓库中,SelectTrigger.jsx 中将其进行了处理

const popupStyle = { ...dropdownStyle };

const widthProp = dropdownMatchSelectWidth ? 'width' : 'minWidth';
if (this.state.dropdownWidth) {
popupStyle[widthProp] = `${this.state.dropdownWidth}px`;
}


这段代码就是 dropdownMatchSelectWidth 生效的代码,如果传入 true,那么就将 width 写死,只能与输入框同宽;如果输入false,那么只是设置一个 min-width,限制宽度其不会小于输入框。

至于其他属性下面又将样式属性传给了 react-Trigger 组件,并最后设置生效,但位置属性是比较特殊的,antd 并没有将这些属性交给开发者控制。

似乎我们的路走到了尽头?换个思路,antd 内部一定存在使用 left 进行定位的逻辑,那么底层有没有这个接口呢,根据它的设计原理,我们只需要在最上层添加这个属性,那么层层传递下去后,一定会被这个属性的逻辑所应用

四:新思路,可有这个接口吗?

答案是有的,在 react-select 中发现其props中有 dropdownAlign 属性,马上跟踪了上去,到最后其实是到了react-align 组件上,他是参照 @yiminghe 的 @dom-align规则,所以最终的代码是

<Select>
showSeach
style={{ width: 100% }}
placeholder="-- 不限 --"
optionFilterProp="children"
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
dropdownMatchSelectWidth={false}
dropdownAlign={{
points: ['tr', 'br'],
overflow: false,
}}
// 生成 <Option>
</Select>


关键位置通知代码在 dropdownAlign,point内成员分别代表 target source 相对的位置,实现的效果是:在 left、top、right、bottom属性为零时,让 target element 的上边和右边与 souce element 的底边和右边对齐,更详细的可以看前面的 dom-align 规则,overflow 则表示 dropdown 不会覆盖输入框

至此需求其实就算暂时完成了,不过我还想另外再考量两个备选方案

五:来两个备选?

很容易想到的是,研究一下源码,在底层可能是用一种比较简单的方法整合在 react-select 中,而我们可以另辟蹊径,用两个组件 Input、Dropdown 组合形成一个下拉框筛选功能,这样样式的问题就比较好解决了,只是中间要加上 组件间数据联动的逻辑。

贴一个大概的框架

const { Menu, Dropdown, Icon, Input
4000
} = antd;

const Search = Input.Search;

const menu = (
<Menu>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="http://www.alipay.com/">1st menu item</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="http://www.taobao.com/">2nd menu item</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="http://www.tmall.com/">3rd menu item</a>
</Menu.Item>
</Menu>
);

ReactDOM.render(
<Dropdown overlay={menu} placement='bottomRight'>
<Search
style={{width: 100}}
/>
</Dropdown>
, mountNode);


初步的效果如下:



数据同步的逻辑就忽略了,数据绑定的双向通信一般来说还是比较容易的,在 Search 中绑定 onChange 确定要展现的 Menu ,在 Dropdown 上绑定点击事件改变 Search 的 value。

另外,之前我们提过下拉框是相对容器进行定位的,而容器由 getPopupContainer 方法控制,默认是绑定在 body 上,相对于页面定位。

在 issue 中有人提出用 getPopupContainer 方法,改变 Dropdown 的容器为自己的 DOM,然后改变容器的定位,以此来随意控制下拉框的位置变化,这种方法显然也是适用于我们的需求的



总结一个,最后其实就是三个方案

推荐方案,其底层封装组件 rc-select 提供 dropdownAlign 接口(类 DOM-align 接口),目前发现存在 CSS 冲突

替代方案,使用 antd 组件 input + dropdown 组合实现功能,需要添加数据同步等逻辑

替代方案(antd issue 讨论区方案),借助 getPopupContainer 替换位置定位容器,并设置好相对定位

收获

antd 底层的了解 包括比较原始的 react-component 组件,高楼大厦的基石往往最美,罗马不是一天建成的,大概在15年 react 比较火的时候,其实很多基础组件就已经有了,一直积累到现在的 n 个好用组件的 antd@3.0,也支持到最新的 react 16

antd使用遇到问题的一般解决思路,一路封装上来其实很多扩展性的东西不会开放,只会对内使用,封装层次越高,设计程度越高,功能集成越强,那么考虑到设计原则冲突,必然留下的更多的通用、强大、简洁、实用的接口,这都是必然现象,所以我们解题的一般思路是

看文档,不要遗漏重要细节

看源码,了解他才能更好的使用它

看issue,你踩过的坑大多数都已经被人踩过了

开issue,真到了开issue的时候,恭喜你,或许你已经走到了绝大多数人前面,或许你也可以考虑提 PR,成为contributeor,从此名利双收,迎娶白富美,走上人生巅峰,成为人生赢家
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息