搞轮子:TabBar标签栏的自救
说到 TabBar 标签栏,我们自然会想到放在 App 底部的标签栏。同时,因为之前开发 Tabs 标签页时,曾经写过一篇文章( 组件开发:从业务到组件Tabs ),讲诉开发 Tabs 的问题和困难。现在遇到的 TabBar 与之大差不差,为什么还要写一篇呢?
原因有二:一是为回顾知识,二是 TabBar 有必要写的知识点
无论是 Tabs 还是 TabBar 组件,都采用父组件控制标签和改变标签,子项提供具体内容的形式
1 | // Tabs |
思绪源码
他们都用了 React.Children
,这是我们要回顾的。
官网:
React.Children
提供了用于处理this.props.children
不透明数据结构的实用方法。
而 TabBar 不同的是,他多了 React.cloneElement
和 React.isValidElement
React.cloneElement
介绍
1 | React.cloneElement( |
官网:以 element 元素为样板克隆并返回新的 React 元素,config 中应包含新的 props,key 或 ref 。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,如果在 config 中未出现 key 或 ref,那么原始元素的 key 和 ref 将被保留
简单来说,他是个拷贝API,一般与 React.Children
配合,在原 children
上加上其他属性
React.isValidElement
介绍
1 | React.isValidElement(object) |
官网:验证对象是否为 React 对象,返回值为 true 或 false
回过头看 TabBar。我们要做的是,不仅要赋予 TabBar.Item 本来就要有的属性,而且要多给它几个属性,如:
- selected:判断它是否被选中。因为 TabBar 控制选择的 key
- onChange:点击后的回调,这里要做”是否能切换标签“的判断,所以要处理
结合三个 React 的顶层 API 后的代码:
1 | ... |
具体代码可以去看 jing-ui 的源代码,这里做分析,先把它所有的子项都遍历,判断它是否是 React 元素,如果不是,返回 null,如果是拷贝子项原来的数据,并给予两个新的 props。其中 getSelected 代码如下:
1 | const getSelected = (index: number, itemKey: string | number) => { |
传入当前子项的索引值以及子项(TabBar.Item)的 itemKey 值,判断 TabBar 的 属性 activeKey 是否不存在,再判断 defaultActiveKey是否有或者索引值是否为0,如果都满足,说明默认选中的key没有,那就赋予第一个子项选中;如果 defaultActiveKey 有,就与 itemKey 匹配;再如果 activeKey 存在,就让它与 itemKey 匹配。其实看代码就能明白
onChildChange 代码:
1 | const onChildChange = (value: string | number) => { |
beforeChange 属性指:切换标签前的回调函数,返回 false 课阻止切换
安全区域的解决
两种方案
一是塞 padding-bottom
样式
.jing-safe-area-bottom { padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); }
1
2
3
4
5
6
7
8
9
10
- 这个方法在 TabBar 上无效,因为 TabBar 注定是底部固定的
二是包个容器,给它一个 height
- ```css
.iphonex-extra-height {
height: constant(safe-area-inset-bottom);
height: env(safe-area-inset-bottom);
}这个方法解决了
在 props 上我们相对应提供
- enableSafeArea:是否开启底部安全区适配,设置 fixed 时默认开启
- fixed:是否固定在底部,默认固定
具体代码如下:
1 | const enableSafeArea = () => safeAreaInsetBottom ?? fixed; |
总结
TabBar 与 Tabs 的不同之处在于,它用了 React.cloneElement,赋予了 (TabBar 中的)children 新的属性,这样我们就能再 TabBar 上控制它是否在切换前使用回调函数,方便我们后续实际业务中的操作