我有两个用react-router创建的<Route>
。
当用户单击“返回列表”时,我想滚动显示用户在列表中的位置。
我该怎么做?
答案 0 :(得分:10)
在codesandbox上的工作示例
React Router v4不提供开箱即用的滚动恢复支持,就目前而言,它们也不会。在他们的文档的React Router V4 - Scroll Restoration部分中,您可以了解更多有关此内容的信息。
因此,尽管我们确实有一些工具可以使这项工作奏效,但每个开发人员都需要编写逻辑来支持这项工作。
.scrollIntoView()
,您可以猜到它会滚动到视图中。支持非常好,目前有93.33%的浏览器支持它。 Source: icanuse
<Link />
组件可以传递状态反应路由器的链接组件具有一个to
道具,您可以提供一个对象而不是一个字符串。这就是这个样子。
<Link to={{ pathname: '/card', state: 9 }}>Card nine</Link>
我们可以使用状态将信息传递给将要呈现的组件。在此示例中,为state分配了一个数字,该数字足以回答您的问题,稍后您将看到,但可以是任何数字。渲染/card
的路线<Card />
现在可以在 props.location.state 处访问变量状态,我们可以根据需要使用它。
渲染各种卡时,我们为每个卡添加一个唯一的类。这样,我们就有了一个可以传递的标识符,并且知道当我们导航回卡片列表概览时,需要将该项目滚动到视图中。
<Cards />
呈现一个列表,每个项目都有一个唯一的类; Link />
将唯一标识符传递给<Card />
; <Card />
呈现卡的详细信息和带有唯一标识符的后退按钮; <Cards />
时,.scrollIntoView()
滚动到以前使用props.location.state
中的数据单击的项目。下面是各个部分的一些代码段。
// Cards component displaying the list of available cards.
// Link's to prop is passed an object where state is set to the unique id.
class Cards extends React.Component {
componentDidMount() {
const item = document.querySelector(
".restore-" + this.props.location.state
);
if (item) {
item.scrollIntoView();
}
}
render() {
const cardKeys = Object.keys(cardData);
return (
<ul className="scroll-list">
{cardKeys.map(id => {
return (
<Link
to={{ pathname: `/cards/${id}`, state: id }}
className={`card-wrapper restore-${id}`}
>
{cardData[id].name}
</Link>
);
})}
</ul>
);
}
}
// Card compoment. Link compoment passes state back to cards compoment
const Card = props => {
const { id } = props.match.params;
return (
<div className="card-details">
<h2>{cardData[id].name}</h2>
<img alt={cardData[id].name} src={cardData[id].image} />
<p>
{cardData[id].description} <a href={cardData[id].url}>More...</a>
</p>
<Link
to={{
pathname: "/cards",
state: props.location.state
}}
>
<button>Return to list</button>
</Link>
</div>
);
};
// App router compoment.
function App() {
return (
<div className="App">
<Router>
<div>
<Route exact path="/cards" component={Cards} />
<Route path="/cards/:id" component={Card} />
</div>
</Router>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
答案 1 :(得分:3)
由于没有关于如何在功能组件中执行此操作的答案,这里是我为项目实现的钩子解决方案:
import React from 'react';
import { useHistory } from 'react-router-dom';
function useScrollMemory(): void {
const history = useHistory<{ scroll: number } | undefined>();
React.useEffect(() => {
const { push, replace } = history;
// Override the history PUSH method to automatically set scroll state.
history.push = (path: string) => {
push(path, { scroll: window.scrollY });
};
// Override the history REPLACE method to automatically set scroll state.
history.replace = (path: string) => {
replace(path, { scroll: window.scrollY });
};
// Listen for location changes and set the scroll position accordingly.
const unregister = history.listen((location, action) => {
window.scrollTo(0, action !== 'POP' ? 0 : location.state?.scroll ?? 0);
});
// Unregister listener when component unmounts.
return () => {
unregister();
};
}, [history]);
}
function App(): JSX.Element {
useScrollMemory();
return <div>My app</div>;
}
使用此覆盖解决方案,您无需担心在所有 Link
元素中传递状态。一个改进是使其通用,因此它向后兼容 push
的 replace
和 history
方法,但在我的特定情况下这不是必需的,所以我省略了它。
我使用的是 react-router-dom
,但您也可以轻松地覆盖 vanilla historyAPI 的方法。
答案 2 :(得分:1)
对此的另一种可能的解决方案是将Porumbei.objects.filter(sex__sexul="male", pairs_as_male__isnull=True)
路线呈现为全屏模式,并保持/cards/:id
路线安装在其后面
答案 3 :(得分:0)
对于使用Redux的全面实施,您可以在CodeSandbox上看到它。
我通过使用历史记录API做到了这一点。
更改路线后保存滚动位置。
当用户单击“后退”按钮时恢复滚动位置。
将滚动位置保存在getSnapshotBeforeUpdate
中,然后将其恢复到componentDidUpdate
。
// Saving scroll position.
getSnapshotBeforeUpdate(prevProps) {
const {
history: { action },
location: { pathname }
} = prevProps;
if (action !== "POP") {
scrollData = { ...scrollData, [pathname]: window.pageYOffset };
}
return null;
}
// Restore scroll position.
componentDidUpdate() {
const {
history: { action },
location: { pathname }
} = this.props;
if (action === "POP") {
if (scrollData[pathname]) {
setTimeout(() =>
window.scrollTo({
left: 0,
top: scrollData[pathname],
behavior: "smooth"
})
);
} else {
setTimeout(window.scrollTo({ left: 0, top: 0 }));
}
} else {
setTimeout(window.scrollTo({ left: 0, top: 0 }));
}
}
答案 4 :(得分:0)
在我们使用功能组件的 React
项目之一中,我遇到了类似的问题。我使用 @Shortchange
和 @Agus Syahputra
提供的答案创建了一个解决方案,因为所提供的确切解决方案由于某种原因在我的情况下不起作用。
我根据 useScrollMemory
答案创建了一个 @Shortchange's
自定义挂钩,但做了一些小改动。此处的 useScrollMemory
函数将 scrollData
对象作为参数,该对象是一个全局对象,根据 @Agus Syahputra's
答案存储访问路径名的滚动位置。 scrollData
对象在 App
组件中初始化。
useScrollMemory.js
import { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
/**
* @description Overrides the PUSH and REPLACE methods in history to
* save the window scroll position for the route.
*
* @param { Object } scrollData - Contains pathname and its scroll position.
*/
const useScrollMemory = (scrollData) => {
const history = useHistory();
useEffect(() => {
const { push, replace } = history;
// Override the history PUSH method to automatically set scroll state.
history.push = (path, state = {}) => {
scrollData[history.location.pathname] = window.scrollY;
push(path, state);
};
// Override the history REPLACE method to automatically set scroll state.
history.replace = (path, state = {}) => {
scrollData[history.location.pathname] = window.scrollY;
replace(path, state);
};
// Listen for location changes and set the scroll position accordingly.
const unregister = history.listen((location, action) => {
window.scrollTo(
0,
action !== 'POP' ? 0 : scrollData[location.pathname] ?? 0,
);
});
// Unregister listener when component unmounts.
return () => {
unregister();
};
}, [history]);
};
export default useScrollMemory;
并在 App
组件中调用 useScrollMemory 开头:
App.js
import useScrollMemory from '../../hooks/useScrollMemory';
const scrollData = {};
const App = () => {
useScrollMemory(scrollData);
return <div>My app</div>;
}
export default App;