为了账号安全,请及时绑定邮箱和手机立即绑定

结合Redux实现一个优雅的弹窗系统

标签:
React.JS

前言

关于提示类组件,在Android里有Toast、SnackBar,React Native里我也发现了一个非常不错的SnackBar风格的开源组件——react-native-message-bar, 具体使用方法就是在应用的顶级容器里注册,然后在你需要invoke的地方调用所给的show方法即可。这让我不禁想起了Redux的状态管理机制,有兴趣的同学们可以研究一下具体的实现方式( 我尽量克服懒惰研究一下哈 ! )

开发缘由

在工作中,因为要给Applean添加各种操作的提示框,最后结合了react-native-message-bar的思路并实现了多层级对话框的叠加显示功能,有兴趣的可以去Applean体验一下

具体实现

这里假定我们已经接入了redux-thunk这个简单而又非常实用的中间件,并且已经处理好了Redux的异步操作(很简单,就是用applyMiddleware接入所需要的Middleware即可),这里因为不符合文章主题,不予讲解。

实现概述

  1. 这里的弹窗是利用react-bootstrap ( bootstrap对reactjs的一个嫁接 ),当然,这个非常非常的次要

  2. 将所需要展示的整个View以message字段的方式通过ActionCreator传递给Reducer,而在Reducer里则以一个数组的形式来管理,这也是为了提高所展示视图灵活性的一个方案。

  3. 在视图的渲染展示上,还是出现了不小的问题,可以详细讲解一下解决方案

ActionCreator部分逻辑

let promptCounter = 0export function showPrompt (payload) { const promptMessage = React.cloneElement(payload.message, {key: promptCounter++, duration: payload.duration}) return {   type: SHOW_PROMPT,   promptMessage: promptMessage
 }
}export function hidePrompt (message) { return {   type: HIDE_PROMPT,   promptMessage: message
 }
}```
* 可以看到,这里我们给传进来的message提示框组件通过`cloneElement`方法给组件key属性加了一个随调用次数自增长的变量值,如果在渲染时再根据遍历所得的index来进行赋值会造成随着数组的变化,元素的key值不断变化。而因为React的diff算法对数组的更新策略是针对unique key值的,如果数组元素没有key值,一来会有系统warning,二来如果某个元素发生变化,会导致系统重绘整个数组。
* `cloneElement`方法中可以看到我们又加了一个duration属性,主要是用来在UI显示层容易拿到这个参数来控制弹框的显示,后文会详细讲到。


### reducer中的处理逻辑

const initialState = {promptMessage: []}
export default function reducer (state = initialState, action = {}) {
case SHOW_PROMPT:
return {
...state,
promptMessage: [...state.promptMessages, action.message]
case HIDE_PROMPT:
return {
...state,
promptMessage: state.promptMessages.filter((x) => x !== action.promptMessage)
}
default:
return state
}
}

这里很简单,我们在遵循不修改原有state的原则下,采取了解构对象以及使用filter函数来实现增加、删除数组元素。当然,assign、拆分数组也未尝不可。### 发起一个对话框

this.props.dispatch(showPrompt{
message: <Modal>Example</Modal>,
duration: 1000
})

就这样,非常简单的发起了一个提示框的Action。### UI展示层处理这里也是多级提示框展示的地方,具体在实现上,也有一些耐人寻味的几点需要注意一下。#### 批量化渲染组件

renderPromptMessage = (promptMessage) => {
const onHide = () => {
setTimeout(() => {
this.props.dispatch(hidePrompt(promptMessage))
}, 1000)

  promptMessage.props.onHide && promptMessage.props.onHide()
}return <ModalWrapper key={'ModalWrapper' + promptMessage.key} child={promptMessage}duration={promptMessage.props.duration} onHide={onHide} />

};

* 以上是对单个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 {
static propTypes = {
child: React.PropTypes.element.isRequired,
onHide: React.PropTypes.func.isRequired,
duration: React.PropTypes.number
};
state = {
show: true
};
onHide = () => {
this.setState({
show: false
})
this.props.onHide()
};
componentDidMount () {
if (this.props.duration) {
this.timer = setTimeout(() => {
this.onHide()
}, this.props.duration)
}
}
render () {
return React.cloneElement(this.props.child, {show: this.state.show, onHide: this.onHide})
}
componentWillUnmount () {
clearTimeout(this.timer)
}
}

作为一个比较核心的类,我们看到它在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逻辑的分离和延迟

> 生活不止眼前的苟且
还有诗和远方的田野



作者:Pober_Wong
链接:https://www.jianshu.com/p/bc8f44facf69


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消