做一个留言板项目 之 实现后退不刷新效果
[React-章节10 实践篇] 做一个留言板项目 之 实现后退不刷新效果
接上篇 《[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的标识了。
看实现
备份数据
componentWillUnmount() {
// 备份当前的页面状态
if (!this.state.isLoading) {
letdata = {
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里。
恢复数据
exportdefault class MsgListPageextends React.Component {
constructor(props, context) {
super(props, context);
// 尝试加载备份的数据
this.tryRestoreComponent();
this.itemsChanged = false; // 本次渲染是否发生了文章列表变化,决定iscroll的refresh调用
this.isTouching = false; // 是否在触屏中
在这里调用tryRestoreComponent(),它尝试从sessionStorage里恢复数据:
tryRestoreComponent() {
letdata = 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这样有什么好处,上面的链接已经说的很清楚,需要好好体会其中的例子。