react-router 如何在页面刷新时保持位置状态?
Posted
技术标签:
【中文标题】react-router 如何在页面刷新时保持位置状态?【英文标题】:How does react-router persist location state over page refreshes? 【发布时间】:2018-12-22 07:35:26 【问题描述】:我真的很困惑。
我有一个工作流程,有人可以开始为产品填写表格。这是一个很长的表格,我想在他们输入时将进度保存到服务器(但直到他们填写一下)。因此,我们从创建表单的 url 开始,在他们输入一段时间后,我们 POST 以在服务器上创建资源,在请求完成后,我们将 url 更新为具有新 id 的编辑路由。
换句话说,您开始在 url /product
处填写表单,然后在您填写了一段时间后,该 url 将转移到 /product/123
。之后,加载该 URL 会为您提供表单。
所以基本上我有
<Route path=`/product` exact component=CreateProduct />
和
<Route exact=true path="/product/:productId" render=(
match: params: productId,
location: state: data=
) => (
<EditProduct productId=productId initialData=data
) />
看到那个状态了吗?那是因为我从创建模式切换到编辑模式的方式是这样的
const id = await apiFetch(`/api/product`, data, method: `POST`)
this.props.history.push(pathname: `/product/$id`, state: data )
在我的<EditProduct>
组件的构造函数中
constructor(productId, initialData)
this.super()
this.state =
if(initialData)
this.setState(data: initialData)
else
getProduct(productId).then((data) => this.setState(data))
通过这样做,<EditProduct>
中的初始数据是从 <CreateProduct>
组件中播种的,我不需要从服务器或其他任何地方重新加载它。
这行得通,过渡顺利,网址更新,一切都很好。
我现在可以继续编辑<EditProduct>
组件并正确保存。我可以打开一个新选项卡到相同的 url,它会加载所有内容,我可以继续。发生这种情况是因为在那种情况下initialData
是undefined
,所以它是从服务器加载的。耶!
但是
如果我改为刷新原始标签,事情会变得很奇怪。自保存以来累积的任何更改都将丢失。在调试器中向下钻取,我发现问题是从 location.state.data
对象传递的 initialData
不是空的 - 它是首次创建产品时的初始对象。
那么它到底是从哪里来的呢?我刚刚进行了整页刷新(甚至是没有缓存和开发工具打开的“硬”刷新)。该数据不在 URL 中(实际上,将 url 复制粘贴到同一窗口中的另一个选项卡中没有这个问题)。
我知道的唯一机制可以在刷新期间保留数据,但不能保存到像这样的新标签页是 sessionStorage
,但是当我在控制台中检查它时,我被告知
> sessionStorage
< Storage length: 0
我什至认为 react-router 可能在页面卸载之前和加载之后操纵会话存储,但是在我的 javascript 包的第一行中断显示完全相同的事情。
那么这种持久性到底是怎么发生的!?
【问题讨论】:
history.state
@azium 是的...我看到一些对它的引用,但没有任何真实信息...听起来可能是...在当前浏览器会话中存储内容?但是不是使用sessionStorage
吗?您能否详细说明或指出一些有关其工作原理的文档。在这一点上,我很少遇到我不知道的浏览器功能,但似乎就是这样。
呃,我不知道浏览器有很多功能,这些功能在 chrome/firefox 中并不总是一致的。您可以深入了解 chromium api 以了解它们如何实现 history
。关于历史的 mdn 文档有点蹩脚(有些页面甚至无法加载)developer.mozilla.org/en-US/docs/Web/API/History。就我个人而言,我不会使用历史状态,而是以某种方式自己管理该状态,或者在 url 或 localstorage 或其他东西中对状态进行字符串化
可能值得考虑将路径参数设为可选:path="/product/:productId?"
。这样,/product
和 /product/123
将使用相同的组件,并且不会在更改时卸载该组件。
html.spec.whatwg.org/dev/history.html 我能找到的最好的
【参考方案1】:
我相信提问者已经解决了这个问题,但答案隐藏在评论中。
问题其实是这样的:
当用户重新加载页面时,state
来自哪里?而state
指的是react-router
提供的props.location.state
TLDR; state
不是纯 javascript 实现,它绑定到浏览器环境。
react-router
的BroswerRouter
直接使用底层原生浏览器历史API,所以历史API 是绑定到平台的,你无法根据正常规则预测它的行为。
这是特殊的部分:
状态对象可以是任何可以序列化的对象。因为 Firefox 将状态对象保存到用户的磁盘中,所以用户重启浏览器后可以恢复它们
大部分用户把state
当作纯javascript,所以有问题
【讨论】:
【参考方案2】:我有一个非常相似的问题,同样的困惑。
用window.history.replaceState()解决了这个问题
我有一个简单的搜索表单,它重定向到第二个页面,并使用来自路由器的位置状态重新填充第二个页面上的搜索输入。
在我的情况下,发生了这种情况:
-
在第一页搜索“foo” -> 重定向到第二页,然后查看“foo”的搜索+结果。
在第二页搜索“bar”。 -> 查看“bar”的结果。
点击刷新。期待?要么是空的搜索栏,要么是“栏”的搜索+结果。 -> 相反,请参阅“foo”的搜索+结果 (??)
我解决了这个问题,这样每次用户在第二页上进行搜索时,我都会用正确的搜索词替换使用 window.history.replaceState
的状态。这种方式刷新为用户提供了预期的搜索。在第二页的每次搜索中将状态替换为空对象也可以正常工作,从而在每次刷新时为用户提供空搜索。
【讨论】:
以上是关于react-router 如何在页面刷新时保持位置状态?的主要内容,如果未能解决你的问题,请参考以下文章