StructureBuilder / react-keep-alive

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

重新active后会丢失scroll位置 #37

Closed qingant closed 5 years ago

qingant commented 5 years ago

试着用bindLifeCycle拦截,手动处理scroll restore,发现在componentWillUnactivate的时候,scrollTop就意外地变成了0

而在onScroll里面每次scroll后记录位置,也会在unactivate后变成0,似乎在挂载到别的地方的时候重新scroll了。

ShenChang618 commented 5 years ago

@qingant执行到 componentWillUnactivate 生命周期的时候,界面已经被卸载了,所以不能再这里记录位置

ShenChang618 commented 5 years ago

@qingant 可以考虑在切换页面的事件上记录位置,比如我点击一个按钮会切换到一个页面,那就可以在按钮的点击事件里写

qingant commented 5 years ago

@qingant 可以考虑在切换页面的事件上记录位置,比如我点击一个按钮会切换到一个页面,那就可以在按钮的点击事件里写

切换页面的事件逻辑往往是在别的组件里面,比方list里面装elem,不可能在elem里面记录list的scroll(当然技术上也可以,但抽象上不太好)

在bindLifeCycle修饰的对象里面,是不是可以得到自己的状态呢?比方处于Unactivate的状态

qingant commented 5 years ago

感谢回复,我现在用这样的办法来解决:

   componentDidActivate() {
        console.log('restore', this.state)
        setTimeout(e => {this.refs.scroller.scrollTop = this.state.scrollTop;}, 10);
        this.state.activated = true
    }
    componentWillUnactivate() {
        this.state.activated = false
    }
    onScroll() {
        if (this.state.activated) {
            this.state.scrollTop = this.refs.scroller && this.refs.scroller.scrollTop
        }
    }

it works!

ShenChang618 commented 5 years ago

@qingant Good

ghost commented 4 years ago

我自己实现一个自动恢复的hook,你可以参考一下:

import React, { useState, useRef, useEffect } from 'react';
import { useKeepAliveEffect } from 'react-keep-alive';

function isEqualAndTruthy(
  newInputs?: React.DependencyList,
  lastInputs?: React.DependencyList
) {
  if (!newInputs || !lastInputs) {
    return false;
  }
  if (newInputs.length !== lastInputs.length) {
    return false;
  }
  for (let i = 0; i < newInputs.length; i++) {
    if (newInputs[i] !== lastInputs[i]) {
      return false;
    }
  }
  return true;
}

export default function useScrollRestoration(deps?: React.DependencyList) {
  const [scrollY, setScrollY] = useState<number | null>(null);

  useKeepAliveEffect(() => {
    let ticking: number | null = null;
    const handler = () => {
      if (ticking === null) {
        ticking = window.requestAnimationFrame(() => {
          setScrollY(window.scrollY);
          ticking = null;
        });
      }
    };
    window.addEventListener('scroll', handler);
    return () => {
      window.removeEventListener('scroll', handler);
      if (ticking !== null) {
        window.cancelAnimationFrame(ticking);
      }
    };
  });

  const savedDeps = useRef(deps);

  const savedCallback = useRef(() => {});

  useEffect(() => {
    savedCallback.current = () => (savedDeps.current = deps);
  });

  useKeepAliveEffect(() => {
    if (scrollY !== null) {
      const y = isEqualAndTruthy(savedDeps.current, deps) ? scrollY : 0;
      window.scrollTo(0, y);
    }
    return () => {
      savedCallback.current();
    };
  });
}

用的时候只需放到对应页面下

export default function PageA() {
  // 依赖发生变化时页面回到顶部,不变时还原滚动位置
  useScrollRestoration([]);
  // ...
}
wl05 commented 4 years ago

@pan-jiaquan 可以,有空研究一下你的代码

wl05 commented 4 years ago

@pan-jiaquan 我的页面是一个滚动加载的页面,我发现会造成同一次请求会触发两次