在 React 单页面应用中,我们经常会遇到列表分页切换刷新的问题。具体表现为:当我们在一个页面上浏览某个列表的第一页时,如果我们点击分页组件中的第二页或其他页码,页面会刷新并回到第一页,而不是跳转到我们点击的页码所对应的数据页。这是一个非常常见的问题,本文将介绍如何解决这个问题。
问题分析
在 React 单页面应用中,当我们点击分页组件中的页码时,实际上是在修改 URL 中的查询参数。例如,我们从第一页的 URL(例如:http://example.com/list?page=1)跳转到第二页的 URL(例如:http://example.com/list?page=2)。这时,React 会重新渲染页面,并根据新的 URL 中的查询参数来加载对应的数据。
然而,当我们在第一页上点击分页组件中的页码时,React 会先卸载当前页面的组件,然后重新加载新的组件。这个过程中,我们的状态和数据都会被重置,因此页面会回到第一页。这就是为什么会出现页面刷新并回到第一页的问题。
解决方案
解决这个问题的思路是:在页面组件加载时,从 URL 中读取查询参数,然后在组件卸载时将查询参数写回 URL。这样,在切换分页时,我们的状态和数据就可以被正确地保存和加载了。
具体实现方式如下:
- 在组件的
componentDidMount
生命周期函数中,读取 URL 中的查询参数,并保存到组件的状态中。
// javascriptcn.com 代码示例 class ListPage extends React.Component { constructor(props) { super(props); this.state = { page: 1, // 默认为第一页 pageSize: 10, // 每页显示10条数据 total: 0, // 数据总数 data: [], // 当前页的数据 }; } componentDidMount() { const { page, pageSize } = this.state; const queryParams = new URLSearchParams(window.location.search); const pageParam = parseInt(queryParams.get('page'), 10); if (!isNaN(pageParam) && pageParam >= 1) { this.setState({ page: pageParam }); } } // ... }
- 在组件的
componentWillUnmount
生命周期函数中,将查询参数写回 URL。
// javascriptcn.com 代码示例 class ListPage extends React.Component { // ... componentWillUnmount() { const { page } = this.state; const queryParams = new URLSearchParams(window.location.search); queryParams.set('page', page); const newUrl = `${window.location.pathname}?${queryParams.toString()}`; window.history.replaceState(null, null, newUrl); } // ... }
- 在分页组件中,将分页链接的
href
属性设置为正确的 URL。
// javascriptcn.com 代码示例 class Pager extends React.Component { render() { const { page, pageSize, total } = this.props; const pageCount = Math.ceil(total / pageSize); const links = []; for (let i = 1; i <= pageCount; i++) { const queryParams = new URLSearchParams(window.location.search); queryParams.set('page', i); const href = `${window.location.pathname}?${queryParams.toString()}`; links.push(<a key={i} href={href}>{i}</a>); } return <div>{links}</div>; } }
示例代码
完整的示例代码如下:
// javascriptcn.com 代码示例 class ListPage extends React.Component { constructor(props) { super(props); this.state = { page: 1, // 默认为第一页 pageSize: 10, // 每页显示10条数据 total: 0, // 数据总数 data: [], // 当前页的数据 }; } componentDidMount() { const { page, pageSize } = this.state; const queryParams = new URLSearchParams(window.location.search); const pageParam = parseInt(queryParams.get('page'), 10); if (!isNaN(pageParam) && pageParam >= 1) { this.setState({ page: pageParam }); } this.loadData(); } componentWillUnmount() { const { page } = this.state; const queryParams = new URLSearchParams(window.location.search); queryParams.set('page', page); const newUrl = `${window.location.pathname}?${queryParams.toString()}`; window.history.replaceState(null, null, newUrl); } async loadData() { const { page, pageSize } = this.state; const response = await fetch(`/api/data?page=${page}&pageSize=${pageSize}`); const { data, total } = await response.json(); this.setState({ data, total }); } handlePageChange(page) { this.setState({ page }, () => this.loadData()); } render() { const { page, pageSize, total, data } = this.state; return ( <div> <ul> {data.map((item) => ( <li key={item.id}>{item.text}</li> ))} </ul> <Pager page={page} pageSize={pageSize} total={total} onPageChange={(page) => this.handlePageChange(page)} /> </div> ); } } class Pager extends React.Component { handleClick(page) { this.props.onPageChange(page); } render() { const { page, pageSize, total } = this.props; const pageCount = Math.ceil(total / pageSize); const links = []; for (let i = 1; i <= pageCount; i++) { const queryParams = new URLSearchParams(window.location.search); queryParams.set('page', i); const href = `${window.location.pathname}?${queryParams.toString()}`; links.push(<a key={i} href={href} onClick={(e) => { e.preventDefault(); this.handleClick(i); }}>{i}</a>); } return <div>{links}</div>; } }
总结
在 React 单页面应用中,列表分页切换刷新是一个常见的问题。我们可以通过保存和加载 URL 中的查询参数来解决这个问题。这个解决方案的核心思路是:在组件的生命周期函数中读取和写回 URL 中的查询参数。这个方案适用于所有需要保存和加载状态的应用场景,具有一定的指导意义。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6550ce49d2f5e1655da9b5b0