需求: 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 !== '' &amp;&amp; this.props.topics.pathname !== prevProps.topics.pathname) {
        const scrollTop = ls.get(pathname) || 0
        ls.remove(pathname)
        window.scrollTo(0, scrollTop)
    }
}