前言
关于提示类组件,在Android里有Toast、SnackBar,React Native里我也发现了一个非常不错的SnackBar风格的开源组件——react-native-message-bar, 具体使用方法就是在应用的顶级容器里注册,然后在你需要invoke的地方调用所给的show
方法即可。这让我不禁想起了Redux的状态管理机制,有兴趣的同学们可以研究一下具体的实现方式( 我尽量克服懒惰研究一下哈 ! )
开发缘由
在工作中,因为要给Applean添加各种操作的提示框,最后结合了react-native-message-bar的思路并实现了多层级对话框的叠加显示功能,有兴趣的可以去Applean体验一下
具体实现
这里假定我们已经接入了redux-thunk这个简单而又非常实用的中间件,并且已经处理好了Redux的异步操作(很简单,就是用applyMiddleware接入所需要的Middleware即可),这里因为不符合文章主题,不予讲解。
实现概述
- 这里的弹窗是利用react-bootstrap ( bootstrap对reactjs的一个嫁接 ),当然,这个非常非常的次要
- 将所需要展示的整个View以message字段的方式通过ActionCreator传递给Reducer,而在Reducer里则以一个数组的形式来管理,这也是为了提高所展示视图灵活性的一个方案。
- 在视图的渲染展示上,还是出现了不小的问题,可以详细讲解一下解决方案
ActionCreator部分逻辑
let promptCounter = 0 |
- 可以看到,这里我们给传进来的message提示框组件通过
cloneElement
方法给组件key属性加了一个随调用次数自增长的变量值,如果在渲染时再根据遍历所得的index来进行赋值会造成随着数组的变化,元素的key值不断变化。而因为React的diff算法对数组的更新策略是针对unique key值的,如果数组元素没有key值,一来会有系统warning,二来如果某个元素发生变化,会导致系统重绘整个数组。 cloneElement
方法中可以看到我们又加了一个duration属性,主要是用来在UI显示层容易拿到这个参数来控制弹框的显示,后文会详细讲到。
reducer中的处理逻辑
const initialState = {promptMessage: []} |
这里很简单,我们在遵循不修改原有state的原则下,采取了解构对象以及使用filter函数来实现增加、删除数组元素。当然,assign、拆分数组也未尝不可。
发起一个对话框
this.props.dispatch(showPrompt{ |
就这样,非常简单的发起了一个提示框的Action。
UI展示层处理
这里也是多级提示框展示的地方,具体在实现上,也有一些耐人寻味的几点需要注意一下。
批量化渲染组件
renderPromptMessage = (promptMessage) => { |
- 以上是对单个promptMessage弹窗的渲染函数,当然,使用的时候,只需要用从State树中获取到的数组调一下map函数就可以啦!
- 这里需要注意的是,如果我们直接采取更新数据源的方式来更新提示窗的数量时,整个View都是以一种非常生硬的方式直接从页面中消失,而非存在一个淡出的过程,这时我们就需要通过在合适的时机调用Modal自带的show参数来让它执行自己的淡出逻辑,而我们是通过ModalWrapper组件来实现的。
- 当然,这里也有几个参数作为属性被传递了进去
- child: 这个在数据逻辑上对应视图层级上关系的一个属性,我们在这里将promptMessage作为属性直接传入作处理,当然也可以用标签对包裹。
- duration: 传入后由Wrapper来控制其fade-out逻辑
- onHide: 这里传入的是在当前函数中写好的onHide方法,主要用来响应Modal组件的onHide回调。可以看到在onHide方法中,我们先是在onHide回调存在时执行了它,然后在一秒后dispatch了我们的hidePrompt动作,这里是数据层面的hide,为什么延时下边会做介绍。
ModalWrapper包装组件
class ModalWrapper extends React.Component { |
作为一个比较核心的类,我们看到它在render函数中返回了一个再次被我们Hack了的child组件(传进来的promptMessage对象)
- show: react-bootstrap 中Modal组件的显示和淡出主要由show属性控制,在正常使用时,可以通过将该属性与state单向绑定来实现对弹框的控制。可以看到在这个组件中,show的初始值是true。
- onHide: 既然show可以直接控制Modal,为何还要写到onHide里呢?这其实也是模拟了Modal.Header中的关闭按钮的逻辑,点击后淡出弹框并执行onHide回调。这个逻辑在没有关闭按钮时是肯定不会触发的。在设置show为false是从视图层面的hide,然后调用传进来的onHide方法,让它执行数据层面的hide,注意这里存在1s的延时,主要用于给Modal足够的时间淡出,否则这个fade-out动画是无法执行完的。
- componentDidMount: 这个也是我们duration的用武之地。在视图渲染完毕后,倒计时duration然后调用上边的onHide方法。
- 干完事情要记得清理战场,为了防止组件被卸载后才执行其对应的逻辑,造成对unmounted componet 操作的bug,我们在组建即将卸载时及时清理掉了这个timer。
结语
本来这篇文章上周就可以完结的,无奈在写的过程中,发现之前使用的thunk中间件可以去掉,而且为了更好地结合关闭按钮和自动关闭逻辑,将hide数据层面的操作从dispatch接收的函数中提取到了Wrapper中。其实最为核心的问题大致总结为以下几点:
- reducer存储数组中message对象的唯一性
- 数组中key值的稳定不可变性
- 数据层和视图层hide逻辑的分离和延迟
生活不止眼前的苟且
还有诗和远方的田野