闽公网安备 35020302035485号
在 H5 日常开发中,会经常遇到列表点击进入详情页面然后返回列表的情况,对于电商类平台尤为常见,像我们平常用的淘宝、京东等电商平台都是做了缓存,而且不只是列表,很多地方都用到了缓存。但刚才说的都是 App,在原生 App 中,页面是一层层的 View,盖在 LastPage 上,天然就能够保存上一个页面的状态。
而 H5 不同,从详情返回到列表后,状态会被清除掉,重新走一遍生命周期,会重新发起请求,会有新的状态写入,对于分页接口,列表很长,当用户翻了好几页后,点击详情看看商品详情后再返回列表,此时页面回到第一页,这样用户体验很差,如果在进入详情的时候将列表数据缓存起来,返回列表的时候用缓存数据,而不是重新请求数据,停留在离开列表页时的浏览位置;或者是能够像 App 那样,将页面一层层堆叠在 LastPage 上,返回的时候展示对应的页面,这样用户体验会好很多,本文简单介绍一下在自己在做列表缓存的时候考虑的几点,后附简单实现。
function RouterConfig({ history, app }) {
const routerData = getRouterData(app);
return (
<ConnectedRouter history={history}>
<Route
path="/"
render={(props) => <Layouts routerData={routerData} {...props} />}
redirectPath="/exception/403"
/>
</ConnectedRouter>
);
}
// 堆代码 duidaima.com
// 路由配置说明(你不用加载整个配置,
// 只需加载一个你想要的根路由,
// 也可以延迟加载这个配置)。
React.render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About}/>
<Route path="users" component={Users}>
<Route path="/user/:userId" component={User}/>
</Route>
<Route path="*" component={NoMatch}/>
</Route>
</Router>
), document.body)
如何解决<!-- 失活的组件将会被缓存!--> <keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive>但是,React 中并没有 keep-alive 这种类似的标签或功能,官方认为这个功能容易造成内存泄漏,暂不考虑支持。
1.持久化缓存。 如果是数据持久化可存到 URL 或 localStorage 中,放到 URL 上有一个很好点在于确定性,易于传播。但 URL 可以先 pass 掉,因为在复杂列表的情况下,需要存的数据比较多,全部放到 URL 是不现实的,即使可以,也会让 URL 显得极其冗长,显然不妥。 localStorage 是一种方式,提供的 getItem、setItem 等 api 也足够支持存取操作,最大支持 5M,容量也够,通过序列化 Serialize 整合也可以满足需求,另外 IndexDB 也不失为一种好的方式,WebSQL 已废弃,就不考虑了。
2.内存。 对于不需要做持久化的列表或数据来说,放内存可能是一个更好的方式,如果进行频繁的读写操作,放内存中操作 I/O 速度快,方便。因此,可以放到 redux 或 rematch 等状态管理工具中,封装一些通用的存取方法,很方便,对于一般的单页应用来说,还可以放到全局的 window 中。
3.看具体的业务场景,来判断取的时机。
在哪取: window
import React from 'react'
import { connect } from 'react-redux'
import cacheHoc from 'utils/cache_hoc'
@connect(mapStateToProps, mapDispatch)
@cacheHoc
export default class extends React.Component {
constructor (...props) {
super(...props)
this.props.withRef(this)
}
// 堆代码 duidaima.com
// 设置 CACHE_NAME
CACHE_NAME = `customerList${this.props.index}`;
scrollDom = null
state = {
orderBy: '2',
loading: false,
num: 1,
dataSource: [],
keyWord: undefined
}
componentDidMount () {
// 设置滚动容器list
this.scrollElRefs = [this.scrollDom]
// 请求数据,更新 state
}
render () {
const { history } = this.props
const { dataSource, orderBy, loading } = this.state
return (
<div className={gcmc('wrapper')}>
<MeScroll
className={gcmc('wrapper')}
getMs={ref => (this.scrollDom = ref)}
loadMore={this.fetchData}
refresh={this.refresh}
up={{
page: {
num: 1, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
size: 15 // 每页数据的数量
// time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
}
}}
down={{ auto: false }}
>
{loading ? (
<div className={gcmc('loading-wrapper')}>
<Loading />
</div>
) : (
dataSource.map(item => (
<Card
key={item.clienteleId}
data={item}
{...this.props}
onClick={() =>
history.push('/detail/id')
}
/>
))
)}
</MeScroll>
<div className={styles['sort']}>
<div className={styles['sort-wrapper']} onClick={this._toSort}>
<span style={{ marginRight: 3 }}>最近下单时间</span>
<img
src={orderBy === '2' ? SORT_UP : SORT_DOWN}
alt='sort'
style={{ width: 10, height: 16 }}
/>
</div>
</div>
</div>
)
}
}
效果如下:

const storeName = 'CACHE_STORAGE'
window[storeName] = {}
export default Comp => {
return class CacheWrapper extends Comp {
constructor (props) {
super(props)
// 堆代码 duidaima.com
// 初始化
if (!window[storeName][this.CACHE_NAME]) {
window[storeName][this.CACHE_NAME] = {}
}
const { history: { action } = {} } = props
// 取 state
if (action === 'POP') {
const { state = {} } = window[storeName][this.CACHE_NAME]
this.state = {
...state,
}
}
}
async componentDidMount () {
if (super.componentDidMount) {
await super.componentDidMount()
}
const { history: { action } = {} } = this.props
if (action !== 'POP') return
const { scrollTops = [] } = window[storeName][this.CACHE_NAME]
const { scrollElRefs = [] } = this
// 取 scrollTop
scrollElRefs.forEach((el, index) => {
if (el && el.scrollTop !== undefined) {
el.scrollTop = scrollTops[index]
}
})
}
componentWillUnmount () {
const { history: { action } = {} } = this.props
if (super.componentWillUnmount) {
super.componentWillUnmount()
}
if (action === 'PUSH') {
const scrollTops = []
const { scrollElRefs = [] } = this
scrollElRefs.forEach(ref => {
if (ref && ref.scrollTop !== undefined) {
scrollTops.push(ref.scrollTop)
}
})
window[storeName][this.CACHE_NAME] = {
state: {
...this.state
},
scrollTops
}
}
if (action === 'POP') {
window[storeName][this.CACHE_NAME] = {}
}
}
}
}