【React】为什么路由跳转时页面滚动高度不会被重置(保留上个页面高度)?理解 history scrollRestoration 的场景与使用,以及如何使用 React Router 重置和跳转前保留滚动高度
哪些场景浏览器会恢复滚动高度?哪些场景会重置高度?
前置场景与条件
列表页进入详情页和当前页面刷新时
[{"url":"https://image.baidu.com/search/down?url=https://gzw.sinaimg.cn/mw2000/0085UwQ9gy1hrl4er4zxyg30m80h4dh5.gif","alt":"tag a","title":""},{"url":"https://image.baidu.com/search/down?url=https://gzw.sinaimg.cn/mw2000/0085UwQ9gy1hrl4er6q1mg30m80h4403.gif","alt":"location href","title":""}]
  使用前进后退按钮和 history api 时
[{"url":"https://image.baidu.com/search/down?url=https://gzw.sinaimg.cn/large/0085UwQ9gy1hrlaefm09cg30m80h440n.gif","alt":"history","title":""},{"url":"https://image.baidu.com/search/down?url=https://gzw.sinaimg.cn/mw2000/0085UwQ9gy1hrlahhs8z9g30p00g277m.gif","alt":"button","title":""}]
  小结
恢复滚动高度的前提
在 React Router 中的表现
使用 history 跳转
ScrollRestoration
function RootRouteComponent() {
  return (
    <div>
      {/* ... */}
      <ScrollRestoration
        getKey={(location, matches) => {
          const paths = ['/home', '/notifications'];
          return paths.includes(location.pathname)
            ? // home and notifications restore by pathname
              location.pathname
            : // everything else by location like the browser
              location.key;
        }}
      />
    </div>
  );
}效果演示
getKey
history 前进后退时也重置滚动高度
const getKey = (location, matches) => {
  return Math.random();
};preventScrollReset
<Link preventScrollReset={true} />
<Form preventScrollReset={true} />
navigate('/home', { preventScrollReset: true, })/packages/react-router-dom/index.tsxlet { restoreScrollPosition, preventScrollReset } = useDataRouterState( DataRouterStateHook.UseScrollRestoration, ); React.useLayoutEffect(() => { // .... // preventScrollReset 为 true 时,不会重置高度 if (preventScrollReset === true) { return; } // otherwise go to the top on new locations window.scrollTo(0, 0); }, [location, restoreScrollPosition, preventScrollReset]);
为什么在开发环境中 刷新 会重置高度?
源码部分
/packages/react-router-dom/index.tsxfunction RouterProvider() { // 订阅来自 react router 的 state 变化,如 updateState React.useLayoutEffect(() => router.subscribe(setState), [router, setState]); } // 高度重置与恢复 function useScrollRestoration({ getKey, storageKey }) { let { restoreScrollPosition, preventScrollReset } = useDataRouterState( DataRouterStateHook.UseScrollRestoration, ); // Trigger manual scroll restoration while we're active React.useEffect(() => { window.history.scrollRestoration = 'manual'; return () => { window.history.scrollRestoration = 'auto'; }; }, []); // window pageHide 事件:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/pagehide_event,单页应用可以通过刷新来测试 usePageHide( React.useCallback(() => { if (navigation.state === 'idle') { let key = (getKey ? getKey(location, matches) : null) || location.key; savedScrollPositions[key] = window.scrollY; } try { sessionStorage.setItem( storageKey || SCROLL_RESTORATION_STORAGE_KEY, JSON.stringify(savedScrollPositions), ); } catch (error) {} window.history.scrollRestoration = 'auto'; }, [storageKey, getKey, navigation.state, location, matches]), ); React.useLayoutEffect(() => { // ... // 启用 `ScrollRestoration`,此处使用 updateState 更改 restoreScrollPosition let disableScrollRestoration = router?.enableScrollRestoration( savedScrollPositions, () => window.scrollY, getKeyWithoutBasename, ); return () => disableScrollRestoration && disableScrollRestoration(); }, [router, basename, getKey]); // 重置高度 React.useLayoutEffect(() => { // .... // otherwise go to the top on new locations window.scrollTo(0, 0); }, [location, restoreScrollPosition, preventScrollReset]); }
function pushEffect(tag, create, destroy, deps) {
  var effect = {
    tag: tag,
    create: create,
    destroy: destroy,
    deps: deps,
    // Circular
    next: null,
  };
  var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
  if (componentUpdateQueue === null) {
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    currentlyRenderingFiber$1.updateQueue = componentUpdateQueue;
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    var lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      var firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 kshao-blog-前端知识记录!
 评论











