杭州电子商务研究院

将 React Router 与 Redux 结合使用

2020-08-28 08:00飞浪

介绍

Redux 和 React Router 是两个最常用的 React 库。Redux 用于应用状态管理,而 React Router 用于路由。在大多数应用中,需要在应用状态和路由器之间进行通信。通常,这是对初始用户操作的响应。

尽管这两个库都被广泛使用,但两者之间的集成并不简单。对此事有不同的看法,每种方法都旨在解决一组特定的问题。在本指南中,我们将探讨两种方法,使我们能够无缝地将 Redux 与 React Router 集成。

就本指南的范围而言,我们假设实现了一个包含三个示例页面的电子商务停止应用程序:

  1. 产品提交页面:用户可以填写表格来提交产品
  2. 产品列表页面:所有提交的产品都列在这里
  3. 产品查看页面:从列表中选择产品后,用户将导航至此处查看单个产品的详细信息

我们不会详细讨论实现,但会在以下代码中重点介绍与 Redux 和 React Router 的实现有关的一些事实。

保持 Redux 和 React Router 分离

鉴于上述示例,您可以首先探索如何让这两个库协同工作而不紧密耦合。由于这两个库都已安装和配置,下一步是观察几个需要在这两个库之间建立通信的实际场景。

1. 根据状态变化改变路线

最常见的 Redux-React Router 交互之一是在某个应用状态更改后执行路由更改。例如,考虑表单提交。理想情况下,您应该将表单状态保存在本地(直接保存在组件状态中或使用 Formik 等库)。触发表单提交后,您将使用表单数据分派 thunk 操作。假设表单提交是为了在您的应用中创建用户,提交成功后,您需要将用户路由回用户表。同样,提交失败后,您需要将用户保留在同一位置(表单 UI)并显示错误反馈。

第一个挑战是检测表单提交完成及其状态。由于 Redux 的单向数据流架构,这一点并不简单。关键问题是 Redux 存储无法访问路由器状态。即使你以某种方式从 Reducer 或 Action 外部访问路由器,这也会通过引入副作用而违反纯函数的条件。

因此,唯一可行的方法是将状态更改传播到组件,并让组件触发路由器更改。为此,您可以在 Redux 存储中保留表单提交状态,并在表单的 API 调用响应中更新变量。以下代码显示了使用上述方法的代码摘录。

      // actions.js

const initState = {
    isSubmitted: false
}

const formSlice = createSlice({
    name: "form",
    initialState: initState,
    reducers: {
        setIsFormSubmitted(state, { action, payload }){
            state.isSubmitted = payload
        },
        // ...
    }
});

export const formActions = formSlice.actions;
export const formReducer = formSlice.reducer;

export function submitForm(formData){
    return async (dispatch, getState) => {
        try{
            const response = await api.submitForm(formData);
            dispatch(formActions.setIsFormSubmitted(true));
        }catch{
            console.error("Error submitting the form");
        }
    }
}

// ...

// ProductSubmissionPage.jsx
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux/lib/hooks/useDispatch';
import * as apiActions from './state';

export function ProductSubmissionPage(){
    const [formState, setFormState] = useState({});
    const isSubmitted = useSelector(state => state.form.isSubmitted);
    const history = useHistory();
    const dispatch = useDispatch();

    useState(() => {
        if(isSubmitted){
            history.push("/products");
        }
    }, [isSubmitted]);

    const submitForm = () => {
        dispatch(apiActions.submitForm(formState));
    }

    return (
        <div>
            ...
        </div>
    )
}
    

您可以看到useHistory钩子的使用情况,它可访问路由器的历史记录实例。在 React Router 的 hooks 之前版本中,您必须通过withRouter HoC(高阶组件)传递需要访问路由器状态的任何组件。随着useHistory钩子的引入,该过程现在变得简单得多。

尽管上述方法按预期工作,但它增加了不必要的复杂性。您被迫在应用程序状态中保留一个表单状态变量,以方便应用程序的路由。因此,处理这种情况的更好方法是将整个表单提交流程集中到组件本身中。这样,您现在可以在同一点实现整个表单控制流并轻松触发路由器更改。下面的代码演示了这个想法。

      // ProductSubmission.jsx
    // ...

    const submitForm = () => {
        try{
            const response = await api.submitForm(formState);
            if(response.message === "OK"){  
                history.push("/products");
            }else{
                console.error("Error submitting the form");
            }
        }catch{
            console.error("Error submitting the form");
        }
    }

    // ...
    

2. 响应路线变更而改变状态

另一个实例是根据路由转换对状态进行更改。例如,考虑一个带有产品列表的电子商务商店应用。用户将从列表中选择一个产品,然后被重定向到类似于https://ecommerce.app/products/123456的链接,其中123456是产品 ID。在这种情况下,应更新应用状态以反映当前活动产品 ID 为123456。

就像前面的情况一样,解决上述问题的最佳方法是通过一个可以访问应用程序状态和路由器状态的渲染组件。使用 useEffect和useParams钩子,您可以监听 URL 的路径参数的变化并相应地更新应用程序状态。

      // ProductViewPage.jsx

import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { useDispatch } from 'react-redux/lib/hooks/useDispatch';
import { productActions } from './state';

export function ProductViewPage(){
    const { productId } = useParams();
    const dispatch = useDispatch();

    useEffect(() => {
        dispatch(productActions.setActiveProduct(productId));
    }, [productId]);

    return (
        <div>
            ...
        </div>
    )
}
    

整合 Redux 与 React Router

如果您从头开始项目,另一个可用的选项是将 React-Router 状态与 Redux 存储集成。您可以使用Connected-react-router库(以前称为react-router-redux)。他们的Github Repo详细介绍了集成步骤。设置完成后,您现在可以直接在 Redux 中访问路由器状态,以及调度操作以在 Redux 操作中修改路由器状态。

优点和缺点

虽然上述两种方法都是将 Redux 与 React Router 集成的完全合法的方法,但每种方法都有一些固有的优点和缺点,正确的选择在很大程度上取决于您的场景。

考虑到易用性,第二种将路由器状态与应用程序状态集成的方法显然是更好的选择。它避免了在路由器和状态之间使用组件进行中介的需要。

但是,如果考虑代码的可读性,您会发现集成方法慢慢开始将关键的实现细节隐藏在实际操作发生的组件之外。例如,之前您描述了从提取 URL 的路径参数到表单提交后的导航的整个生命周期,这些都在同一组件代码中完成。这比将部分实现隐藏在不同代码文件中的 Reducer 和操作代码中更加冗长和具有声明性。

结论

在本指南中,我们探讨了两种使用 React Router 和 Redux 的方法。虽然这两种方法都有效,但各有优缺点。对于规模合理的项目,我建议采用第一种方法,因为它将事件的关键流程放在同一个地方,而不是分散在多个文件中。

曝光28735浏览1555