【React】为什么路由跳转时页面滚动高度不会被重置(保留上个页面高度)?理解 history scrollRestoration 的场景与使用,以及如何使用 React Router 重置和跳转前保留滚动高度
哪些场景浏览器会恢复滚动高度?哪些场景会重置高度?
前置场景与条件
列表页进入详情页和当前页面刷新时
[{"url":"https://image.baidu.com/search/down?url=https://gzw.sinaimg.cn/mw2000/0085UwQ9gy1hrl4er4zxyg30m80h4dh5.gif","alt":"tag a"},{"url":"https://image.baidu.com/search/down?url=https://gzw.sinaimg.cn/mw2000/0085UwQ9gy1hrl4er6q1mg30m80h4403.gif","alt":"location href"}]
使用前进后退按钮和 history api
时
[{"url":"https://image.baidu.com/search/down?url=https://gzw.sinaimg.cn/large/0085UwQ9gy1hrlaefm09cg30m80h440n.gif","alt":"history"},{"url":"https://image.baidu.com/search/down?url=https://gzw.sinaimg.cn/mw2000/0085UwQ9gy1hrlahhs8z9g30p00g277m.gif","alt":"button"}]
小结
恢复滚动高度的前提
在 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, })
let { 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]);
为什么在开发环境中 刷新
会重置高度?
源码部分
function 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-前端知识记录!
评论