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

kmpatx3s  于 12个月前  发布在  其他
关注(0)|答案(7)|浏览(118)

我有一个搜索面板的网页。搜索面板有几个输入字段:ID,大小,.
我想要的是当用户设置搜索值(例如:id=123和size=45)并按下搜索按钮时:

  1. Redux reducer中的searchState应该更新为新的搜索值(id=123和size=45)
  2. URL应该改为“http://mydomain/home?id=123&size=45
    另一方面,如果用户将URL更改为http://mydomain/home?id=111&size=22,则:
  3. reducer中的searchState应该使用新的搜索值(id=111和size=22)进行更改
  4. UI搜索面板输入应更新为新值(id=111和size=22)
    如何达到目标?我应该使用什么路由器(react-router,redux-router,react-router-redux或其他)?
p8h8hvxi

p8h8hvxi1#

我建议直接使用React Router不要在Redux中保留searchState。React Router会将URL参数注入到你的组件中,你可以在mapStateToProps(state, ownProps)中使用它们来计算最终的props。
如果你真的想看到路由变化作为动作,你可以使用react-router-redux进行双向同步,但它不会给予你状态中的参数-只是当前位置。如果你想记录和重放动作,并让URL栏在重放时更新,这是唯一的用例。
这绝不是必需的-在大多数情况下,**直接使用React Router就足够了。

yyhrrdl8

yyhrrdl82#

  • 简而言之 *:您可以使用redux-query-sync来保持存储中的URL参数和字段同步。它无需任何路由器即可工作。
  • 更长的故事 *:在同一个问题上挣扎,我首先欣赏丹·阿布拉莫夫的回答,他建议(用我的话说)把URL看作是“第二个商店”,是Redux商店之外的应用程序状态的一部分。但是后来我发现,有两种“商店”使得修改代码变得困难,例如将东西从一个移动到另一个,因为每个“商店”都有不同的接口。作为一个开发人员,我宁愿选择Redux状态中的任何字段,并在URL中 * 暴露 * 它们,就好像位置是我状态(的一部分)的一个小窗口。

因此,我刚刚发布了redux-query-sync,让您提供一个选择器函数,用于从状态中选择一个值,然后将其暴露在您指定的参数名称的窗口位置。(例如,当应用程序初始加载时),您可以提供一个action creator,它将被传递参数值,因此您的reducer可以相应地更新状态。

ukqbszuj

ukqbszuj3#

我最近也完成了我公司应用程序的这个功能。我们想在URL中添加不同的搜索查询。
我想在这里分享几件事:
1)我们使用了Redux store和react-router。由于我们还使用了Typescript,因此我们最终使用了react-router中的RouteWebsentProps来使用this.props.history.push()来更新URL。
2)我们将所有搜索查询保留在Redux store中。更新Redux store和URL的工作流程如下:
在app中选择一些过滤选项=> Dispatch actions to update filtering state in Redux store => update the URL
3)最后,我们还希望用户能够输入带有查询参数的URL,它将更新应用程序中的所有过滤器。要做到这一点,工作流程甚至更简单:
用户输入URL => dispatch actions,通过URL中的查询参数更新Redux状态。Redux状态的更新将自动导致应用中的重新渲染,所有过滤器都将更新。
因此,这里最重要的事情是始终保持Redux状态和URL彼此同步。

d6kp6zgx

d6kp6zgx4#

这是我处理第一种情况的方法:

  • 当输入值改变时,它的组件会触发一个回调,该回调作为一个prop从它的容器中传递。
  • 在回调中,容器在事件发生时调度负责更新Redux状态的操作。
  • 在紧接着action调用的行中,我使用this.context.router.push(),并将带有正确查询字符串的url传递给它。

我不确定这是否是正确的方法,我发现它比首先更新URL更好,因为在我看来,查询字符串应该是状态的反映,而不是状态的主人。
至于反向的情况,我真的不确定。似乎手动设置URL会触发完全重新加载,但我可能搞错了。
至于使用哪个路由器,我使用的是React Router本身。我并没有真正发现使用其他路由器的价值,this discussion对我来说是决定性的。

htzpubme

htzpubme5#

我最近完成了一个类似的问题。我使用MobX作为我的商店和redux-router作为我的路由器。(我用react-router做得很好,但我需要在组件之外分派一个push动作- redux作为全局商店在这里工作得很好)
我的解决方案与cantera所描述的类似--我的路由器状态仅仅是表单状态的一个反映。这有一个额外的好处,那就是不必放弃表单状态,而完全依赖于路由器状态。
在第一种情况下,
1)我像往常一样更新表单输入,这会触发结果组件中的重新呈现。
2)在results组件的componentDidUpdate()中,我使用表单props构造更新后的查询字符串。
3)我将更新后的查询字符串推送到路由器状态。这纯粹是为了更新URL的效果。
对于第二方案所
1)在根Route组件上的onEnter钩子中,我获取可用的查询字符串并将其解析为初始形式值。
2)我用这些值更新我的存储。这只适用于第一次加载页面,也是路由器状态决定表单状态的唯一一次。
编辑:此解决方案不考虑当您返回浏览器历史记录时的情况,因为URL不会更新您的状态。

brjng4g3

brjng4g36#

我推荐新的工具react-url-query,它可以非常直接地完成同步。

8cdiaqws

8cdiaqws7#

这里有一个解决方案,可以很好地控制浏览器url中的内容。
解决方案由以下部分组成:

  1. queryStringSelector-一个选择器,负责构建一个查询搜索字符串,并将其推送到浏览器的url中。它可以是智能的,也可以省略默认值。可以实现自定义序列化值,也可以只使用JSON.stringify
  2. getPartialStateFromQueryString helper fn。它获取浏览器的查询字符串并将其解析为Partial<MyState>。它应该支持所有由queryStringSelector编码的字段,并且必须能够将字符串值解析回所需的结构。它不应该信任URL中的值,因为用户可能会编辑这些字符串。
  3. reducer中的setStateFromPartialState action。它获取存储(切片)的部分状态作为有效负载,并更新状态以匹配该部分状态。所有应与URL同步但不存在于部分状态对象中的值应首先重置为初始状态。
  4. useSyncWithUrl自定义钩子,主要负责三个方面:第一设置浏览器url的初始值,在组件挂载时存储,只做一次;第二订阅状态变化,基本上就是消耗queryStringSelector的输出,并推送更新到URL;第三在URL变化时更新状态,简单来说,它订阅浏览器中的导航更改(“POP”事件)并相应地更新存储。请注意,它不会对PUSH事件做出React,只是对“POP”事件做出React
    请务必记住,建议的方法只有在您使用商店作为真实来源时才有效。即您的应用应该将更改应用于商店而不是URL以达到特定状态。URL仅在两种例外情况下被视为真实来源:1)初始化时2)当浏览器的向后导航发生时。但是,如果您使用应用程序自己的同步钩子示例导航到应用程序的其他部分,它仍然有效,因为无论您如何到达该状态,都会发生初始化阶段。
    示例如下:
    查询字符串选择器:
export const queryStringSelector = createSelector([rootSelector], (root) => {
  const params = new URLSearchParams();
  if (root.search !== initialState.search) {
    params.append('search', root.search);
  }

  if (root.result_tab !== initialState.result_tab) {
    params.append('tab', root.result_tab);
  }

  const queryString = params.toString();
  if (!queryString) {
    return '';
  }
  return `?${queryString}`;
}

字符串
getPartialStateFromQueryString辅助程序

const getPartialStateFromQueryString = (queryString: string): Partial<MyState> => {
  const searchParams = new URLSearchParams(queryString);
  const partialState: Partial<MyState> = {};

  const search = searchParams.get('search');
  partialState.search = search || '';

  const tabString = searchParams.get('tab');
  if (tabString) {
    partialState.result_tab = tabString as any;
  }

  return partialState;
};


减速器作用

const keysSyncedToUrl: (keyof MyState)[] = ['search', 'result_type', /* ... */];
//
setStateFromPartialState: (state, { payload }: PayloadAction<Partial<MyState>>) => {
  // set all keys to default state first
  keysSyncedToUrl.forEach((key: string) => {
    state[key] = initialState[key] as any;
  });

  // apply partial state on top of initial state
  Object.entries(payload).forEach(([key, value]) => {
    state[key] = value;
  });
}


同步到url自定义挂钩:

export const useSyncWithUrl = () => {
  const { search } = useLocation();
  const history = useHistory();
  const dispatch = useDispatch();
  const [isSynced, setSynced] = useState<boolean>(false);
  const queryString = useSelector(queryStringSelector);

  // updates url when state changes
  useEffect(() => {
    if (!isSynced) {
      return;
    }
    if (queryString !== search) {
      history.push({ ...location, search: queryString });
    }
  }, [isSynced, queryString]);

  // apply params from url on initialization
  useEffect(() => {
    if (!search) {
      setSynced(true);
      return; // no search params to restore, exit
    }
    const partialState = getPartialStateFromQueryString(search);

    dispatch(mySlice.actions.setStateFromPartialState(partialState));

    setSynced(true);
  }, []);

  // react on navigation events (back-forward) and restore state from url
  useEffect(() => {
    const unsubscribe = history.listen((location, actionType) => {
      if (actionType === 'POP') {
        const partialState = getPartialStateFromQueryString(location.search);
        dispatch(mySlice.actions.setStateFromPartialState(partialState ));
      }
    });
    return unsubscribe;
  }, [history]);
};

相关问题