如何同步 Redux 状态和 url 查询参数

Posted

技术标签:

【中文标题】如何同步 Redux 状态和 url 查询参数【英文标题】:How to sync Redux state and url query params 【发布时间】:2016-08-04 11:11:27 【问题描述】:

我有一个带有搜索面板的网页。搜索面板有几个输入字段:id、size、...

我想要的是当用户设置搜索值(例如:id=123 和 size=45)并按下搜索按钮时:

    Redux reducer 中的 searchState 应使用新的搜索值(id=123 和 size=45)进行更新 网址应改为“http://mydomain/home?id=123&size=45”

另一方面,如果用户将 URL 更改为 http://mydomain/home?id=111&size=22,则:

    reducer 中的 searchState 应更改为新的搜索值(id=111 和 size=22) 应使用新值更新 UI 搜索面板输入(id=111 和 size=22)

如何达到目标?我应该使用什么路由器(react-router、redux-router、react-router-redux 等)?

【问题讨论】:

【参考方案1】:

我建议直接使用 React Router 而不要在 Redux 中保留 searchState。 React Router 会将 URL 参数注入到你的组件中,你可以在 mapStateToProps(state, ownProps) 中使用它们来计算最终的 props。

如果您真的想将路线更改视为操作,您可以使用react-router-redux 进行双向同步,但它不会为您提供状态中的参数——只是当前位置。如果您想记录和重播操作,并在重播操作时更新 URL 栏,这是它的唯一用例。

这绝不是必需的——在大多数情况下,直接使用 React Router 就足够了。

【讨论】:

嗨@Dan,我们面临着一个非常相似的问题,就像安东一样。你的回答很有希望,但我们很难找到一个很好的例子,其中 React Router 甚至 React Router 与 Redux 结合改变了几个不同 url 的一个组件的状态。在我们的例子中,我们更改了哈希标签,它应该始终只应用于一个组件。 只有一个路由,并且使用路由注入的props来控制这个组件?这确实是一个与 Redux 无关的单独问题,您可能想单独提出。 这是我的新问题。如果你能看一看,那就太好了。 ***.com/questions/36722584/… 在选择器中使用路由参数怎么样?必须将其从组件发送到选择器似乎有点愚蠢,并且可能导致大量重复 如果搜索值是 only 在 URL 中,这很好用。考虑一下如果你有另一个来自不同来源(例如 redux 商店)的搜索过滤器会发生什么。然后,当您要重置所有过滤器时,您需要更新两个位置(URL 和 redux 存储),这会触发对 props 的两次更新,这会触发两次提取。如何避免这种情况?【参考方案2】:

简而言之:您可以使用redux-query-sync 使存储中的 URL 参数和字段保持同步。它可以在没有任何路由器的情况下工作。


更长的故事: 为同样的问题苦苦挣扎,我首先赞赏 Dan Abramov 的 answer,建议(用我的话)将 URL 视为“第二个存储”,即 Redux 存储之外的应用程序状态的一部分。但后来我发现拥有两种“商店”使得修改代码变得困难,例如将事物从一个转移到另一个,因为每个“商店”都有不同的界面。作为开发人员,我宁愿选择我的 Redux 状态中的任何字段,并将它们公开在 URL 中,就好像该位置是我状态(的一部分)的一个小窗口。

因此我刚刚发布了 redux-query-sync,让你提供一个选择器函数来从状态中选择一个值,然后在你指定的参数名称处暴露在窗口位置。为了让它在另一个方向上同步,从而从 URL 到 Redux 状态(例如,当应用程序最初加载时),你可以提供一个 action creator 将传递参数值,这样你的 reducer 可以相应地更新状态。

【讨论】:

【参考方案3】:

我最近还为我公司的应用完成了此功能的开发。我们想向 URL 添加不同的搜索查询。

我想在这里分享几件事:

1) 我们使用了 Redux 存储和 react-router。由于我们也使用了 Typescript,我们最终使用 react-router 中的 RouteComponentProps 来使用 this.props.history.push() 来更新 URL。

2) 我们将所有搜索查询保存在 Redux 存储中。更新 Redux 存储然后更新 URL 的工作流程如下:

在应用程序中选择一些过滤选项 => 调度操作以更新 Redux 商店中的过滤状态 => 更新 URL

3) 最后,我们还希望用户能够输入带有查询参数的 URL,它将更新应用程序中的所有过滤器。为此,工作流程更加简单:

用户输入 URL => 调度操作以使用 URL 中的查询参数更新 Redux 状态。 Redux 状态的更新会自动导致应用重新渲染,所有过滤器都会更新。

所以这里最重要的是始终保持 Redux 状态和 URL 彼此同步。

【讨论】:

【参考方案4】:

这是我处理第一个场景的方式:

当输入值发生变化时,其组件会触发一个回调,该回调是从其容器作为道具传递的。

在回调中,容器在事件发生时调度负责更新 Redux 状态的动作。

在紧跟在操作调用之后的行中,我使用 this.context.router.push() 并将带有正确查询字符串的 url 传递给它。

我不确定这是正确的方法。我发现最好先更新 URL,因为在我看来,查询字符串应该是状态的反映,而不是状态的主人。

关于相反的情况,我真的不确定。似乎手动设置 URL 会触发完全重新加载,但我可能弄错了。

至于使用哪个路由器,我自己使用的是 React Router。我并没有真正发现使用其他人的价值,this discussion 对我来说是关键。

【讨论】:

我看到的这种方法的问题是,一旦容器中有多个组件更改了 url,它就会纠缠回调和 url 参数更新的混乱。可以在每个组件中执行此操作,而不是使用对容器的回调......【参考方案5】:

我最近完成了与此类似的问题的工作。我使用 MobX 作为我的 store 和 redux-router 作为我的路由器。 (我在 react-router 上做得很好,但是我需要在组件之外调度一个推送操作 - redux 作为一个全局存储在这里工作得很好)

我的解决方案类似于 cantera 所描述的 - 我的路由器状态只是表单状态的反映。这还有一个额外的好处,那就是不必放弃我的表单状态并完全依赖于路由器状态。


在第一个场景中,

1) 我照常更新表单输入,这会触发结果组件中的重新渲染。

2) 在结果组件的componentDidUpdate() 中,我使用表单道具来构造更新后的查询字符串。

3) 我将更新后的查询字符串推送到路由器状态。这纯粹是为了更新网址的效果。


对于第二种情况

1) 在根Route 组件上的onEnter 挂钩中,我获取可用的查询字符串并将其解析为初始表单值。

2) 我用这些值更新了我的商店。这仅适用于页面的首次加载,也是路由器状态决定表单状态的唯一时间。


编辑:当您返回浏览器历史记录时,此解决方案不考虑这种情况,因为 url 不会更新您的状态。

【讨论】:

【参考方案6】:

我会推荐新工具react-url-query,它可以非常直接地进行同步。

【讨论】:

还有一个钩子版本,use-query-params,来自同一作者:github.com/pbeshai/use-query-params

以上是关于如何同步 Redux 状态和 url 查询参数的主要内容,如果未能解决你的问题,请参考以下文章

django 注册 redux URL 受具有多个查询参数的 url 影响

触发 Redux 操作以响应 React Router 中的路由转换

使用 Angular ui-router 设置 URL 查询参数而不改变状态

如何从服务器端 express/mongo/mongoose 中的 URL,客户端的 axios/react/redux 中获取参数 ID

Redux Toolkit RTK Query 发送查询参数

带有 Typescript 和 react-redux 的有状态组件:“typeof MyClass”类型的参数不可分配给 Component 类型的参数