需求: 1、需要路由切换动画, 2、从详情页返回列表页时, 需要滚动到历史位置
咋一看, 这不挺简单的吗?
使用redux
做数据的临时存储, 在componentWillUnmount
钩子里, 读取滚动条位置, 然后保存到本地存储
当从详情页或者其他页面返回列表页时, 判断redux
里是否有数据, 如果有数据直接读取, 不再发起请求, 在componentDidMount
钩子里读取本地存储的滚动条位置, 然后滚动到相应位置不就行了吗
上面的思路在没有路由动画时, 是可行的, 但是有路由动画的话, 就不行了, 你会发现在componentWillUnmount
钩子里取到的滚动条位置, 总是不对的
主要原因是, 为了用户体验, 在详情页需要componentDidMount
钩子里将滚动条设置为0, 不然在进入详情页时, 滚动条位置会不在顶部
再加上, 添加了路由动画后, 从列表页进入详情页, 有一小段时间, 列表页和详情页这两个组件会同时存在, 而且两个页面钩子的执行顺序为: 详情页的componentDidMount
=> 列表页的componentWillUnmount
, 所以问题看的出来了
说了半天废话, 还是说解决方案吧, 依靠 react 的钩子是不行了, 但是还可以靠路由的Prompt
组件
import { Prompt } from 'react-router-dom'
render() {
return (
<div className="main wrap">
<Prompt
when
message={() => {
const path = this.props.location.pathname
const scrollTop = Math.max(window.pageYOffset, document.documentElement.scrollTop, document.body.scrollTop)
ls.set(path, scrollTop)
return true
}}
/>
<div>.........</div>
</div>
)
}
滚动条的位置是记录下来, 接下来的问题就是这么还原到对应位置, 这里以redux
为例:
// 空数据时
this.props.topics => {
data: [],
hasNext: 0,
page: 1,
pathname: ''
}
// 有数据时
this.props.topics => {
data: [{...}, {...}, {...}],
hasNext: 0,
page: 1,
pathname: '/'
}
1, redux 已存在列表数据
那么只要在componentDidMount
读取滚动条位置, 并滚动即可
componentDidMount() {
console.log(`topics: componentDidMount`)
const pathname = this.props.location.pathname
if (this.props.topics.pathname !== '') {
const scrollTop = ls.get(pathname) || 0
ls.remove(pathname)
window.scrollTo(0, scrollTop)
}
}
2, redux 不存在列表数据, 需要异步请求时, 通过componentDidUpdate
钩子判断列表是否渲染完成
componentDidUpdate(prevProps) {
const pathname = this.props.location.pathname
if (this.props.topics.pathname !== '' && this.props.topics.pathname !== prevProps.topics.pathname) {
const scrollTop = ls.get(pathname) || 0
ls.remove(pathname)
window.scrollTo(0, scrollTop)
}
}