接上篇《[React-章节9 实践篇] 做一个留言板项目 之 实现Loading特效》。
背景
react在切换组件时,前一个组件会经历完整的组件销毁过程,后一个组件会经历完整的组件加载过程,具体可以回顾react的生命周期。
因为这个原因,当我从详情页返回到列表页的时候会发现列表页重新渲染回到了初始的状态,并不是一个合理的用户体验。这个问题只有单页应用会面临,因为对于多标签页的应用来说,每个标签页的内容互不影响。
思路
为了解决这个问题,我的基本思路是在离开列表页之前保存当前的列表内容和滚动轴偏移到某处,在回到列表页之后从某处取回列表内容和偏移量并调整整个页面状态到原先的状态,那么用户感受起来就和没有变化一样。
存储的选择
因为保存列表页的浏览状态是一个临时的行为,用户下一次打开app还是希望获得一个崭新的页面,所以我选了html5的sessionStorage,它在标签页关闭后将自动释放。
保存的时机
这是很重要的一点,恰好react生命周期中有一个回调componentWillUnmount(),它在组件卸载之前调用,我们可以在这里保存所有状态以备后用。
如何保存
react-router默认基于浏览器hash进行路由,例如这样的url代表一次列表页访问:http://localhost:8080/#/?_k=1j7gct,其中/#/?_k=1j7gct叫做一个location,react-router会帮我们维护一个location的栈结构叫做history,代表了我们此前的访问路径。
那么_k=xxx是什么东西呢?如果没有这个东东,当我们面临:从组件自身跳转到组件自身的访问路径时,history里就变成了这样[/#, /#],没法标识2个location的差异,因此react-router会帮我们给每个location加上一个全局唯一的随机码_k。
假设这样一个访问路径,首先进入列表页/#/?_k=1j7gct(history=[/#/?_k=1j7gc]),之后跳转到详情页/#/msg-detail-page/18?_k=nuqncq(history=[/#/?_k=1j7gc,/#/msg-detail-page/18?_k=nuqncq])。此时,我们后退到列表页,其实就是从history里pop出/#/msg-detail-page/18?_k=nuqncq,因此我们可以知道列表页地址仍旧保持在/#/?_k=1j7gc,因此_k=1j7gc就顺理成章的成为了我们作为缓存key的标识了。
看实现
备份数据
1 2 3 4 5 6 7 8 9 10 11 12 13 |
componentWillUnmount() { // 备份当前的页面状态 if (!this.state.isLoading) { let data = { items: this.state.items, page: this.page, y: this.iScrollInstance.y, }; window.sessionStorage.setItem(this.props.location.key, JSON.stringify(data)); } else { window.sessionStorage.removeItem(this.props.location.key); } } |
在这里,如果当前页面已经loading首屏完成,那么说明iscroll初始化完成。因此,将列表内容,页码,滚动条偏移一起存储到sessionStorage里。
恢复数据
1 2 3 4 5 6 7 8 |
export default class MsgListPage extends React.Component { constructor(props, context) { super(props, context); // 尝试加载备份的数据 this.tryRestoreComponent(); this.itemsChanged = false; // 本次渲染是否发生了文章列表变化,决定iscroll的refresh调用 this.isTouching = false; // 是否在触屏中 |
在这里调用tryRestoreComponent(),它尝试从sessionStorage里恢复数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
tryRestoreComponent() { let data = window.sessionStorage.getItem(this.props.location.key); // 恢复之前状态 if (data) { data = JSON.parse(data); this.state = { items: data.items, pullDownStatus: 0, // 下拉状态 pullUpStatus: 0, // 上拉状态 isLoading: false, // 是否处于首屏加载中 }; this.page = data.page; } else { this.state = { items: [], // 文章列表 pullDownStatus: 3, // 下拉状态 pullUpStatus: 0, // 上拉状态 isLoading: true, // 是否处于首屏加载中 }; this.page = 1; // 当前翻页 } } |
体验
源码
redux
经过思考,发现实现这个需求本身并不需要redux,而redux到底解决了什么问题呢?读完这个就有感觉了:redux。
归根结底,react中使用redux就是要实现下面这样的效果, 其实完全可以不使用redux,重要的是容器组件和展示组件脱离的思想:
容器组件 | 展示组件 | |
---|---|---|
Location | 最顶层,路由处理 | 中间和子组件 |
Aware of Redux | 是 | 否 |
读取数据 | 从 Redux 获取 state | 从 props 获取数据 |
修改数据 | 向 Redux 派发 actions | 从 props 调用回调函数 |
至于redux这样有什么好处,上面的链接已经说的很清楚,需要好好体会其中的例子。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

可以使用 react-router 做页面切换,但Route 对应path 没有匹配时会卸载掉注册的组件,在重新match 时重新挂载,也会造成刷新的情况
研究了一下route 的源码,发现可以从其 children prop 入手,手动控制 match 在两种状态下组件的渲染规则,用“隐藏组件”替代“卸载组件”,就可以保留全部的数据和交互状态,整理成了轮子,增加了两个自定义的生命周期 didCache didRecover
https://github.com/CJY0208/react-router-cache-route
学习了,我已弃坑 #_#
Pingback引用通告: React.js系列学习-Java小咖秀
谢谢.博主辛苦了健身这种事,不是贵在坚持,而是贵在会员费和私教费上。by http://go2learn.net/category/yibingongxukemu/
1
1