StructureBuilder / react-keep-alive

A component that maintains component state and avoids repeated re-rendering.
MIT License
984 stars 106 forks source link

未能保留组件中滚动容器的滚动位置 #4

Closed CJY0208 closed 5 years ago

CJY0208 commented 5 years ago

测试时发现这个情况

使用 <KeepAlive /> 包裹长列表组件后,列表滚动至底部并触发 keep-alive,缓存恢复时,长列表的 scrollTop 恢复为 0

猜测和 createPortal 工作方式有关,或者是不是我的使用方式有误?

ShenChang618 commented 5 years ago

@CJY0208 这是因为每次移动 DOM 的时候,都会自动重置滚动条高度,Vue 的 <keep-alive> 组件也是如此的。可以看下这个例子 Control cache 手动修改滚动条。

CJY0208 commented 5 years ago

OK,了解了,可否尝试将保存滚动位置的行为纳入到 KeepAlive 中?例如

<KeepAlive>
  {({ saveScrollTop }) => (
    <Comp ref={comp => {
      saveScrollTop(comp.scrollContainer)
    }} />
  )}
</KeepAlive>

或者将 saveScrollTop 行为注入到 Comp props

ShenChang618 commented 5 years ago

@CJY0208 好问题,原先我也有考虑过。不过考虑到用户在一个组件中可能会有多个想要保存的滚动条位置,因此没有在库中做这些操作。

CJY0208 commented 5 years ago

如果有多个滚动容器的话,可考虑 saveScrollTop 注入 Comp props 并允许调用多次

在组件被缓存时将所有订阅了 saveScrollTop 需求的容器 scrollTop 记住,并于组件恢复时恢复各订阅容器的 scrollTop,感觉会是一个不错的功能

ShenChang618 commented 5 years ago

@CJY0208 这个功能我觉得通过 bindLifecycle 这个高阶函数绑定组件,然后在组件的生命周期里自己设置保存的滚动值会好一些。

@bindLifecycle
class List extends React.Component {
  static defaultProps = {
    onMount: () => {}
  };

  listScrollTop = 0;

  componentDidMount() {
    console.log("componentDidMount");
    const { setControlDisabled } = this.props;
    // 5. Disabled for each mount component
    setControlDisabled("List", true);
    setTimeout(() => {
      document.querySelector("#root").scrollTop = this.listScrollTop;
    });
  }

  componentWillUnmount() {
    console.log("componentWillUnmount");
  }

  handleEnterDetails = () => {
    const { setControlDisabled } = this.props;
    // 6. Need to keep the cache after going to the next page
    setControlDisabled("List", false);
    this.listScrollTop = document.querySelector("#root").scrollTop;
  };

  render() {
    return (
      <div className="list-wrapper">
        <h1>This is list</h1>
        <p>
          <Link to="/">Back to home</Link>
        </p>
        <div className="list">
          {new Array(20).fill(null).map((v, index) => (
            <div className="list-item">
              This is <span style={{ color: "#f00" }}>{index}</span>
              <Content />
              <br />
              <Link to="/details" onClick={this.handleEnterDetails}>
                Click to enter details
              </Link>
            </div>
          ))}
        </div>
      </div>
    );
  }
}

react-router-cache-route 中,通过生命周期应该也能轻易实现这个功能。

import React, { Component } from 'react'

export default class List extends Component {
  constructor(props, ...args) {
    super(props, ...args)

    props.cacheLifecycles.didCache(this.componentDidCache)
    props.cacheLifecycles.didRecover(this.componentDidRecover)
  }

  componentDidCache = () => {
    console.log('List cached')
  }

  componentDidRecover = () => {
    console.log('List recovered')
  }

  render() {
    return (
      // ...
    )
  }
}