问题
在本章的其他小节中,我们已经看到可以使用一个称为reducer的纯函数来管理复杂的组件状态。reducer简化了组件,使业务逻辑更易于测试。
但是,如果你有一些数据(如购物篮)需要在应用中被多处访问,那么该怎么办呢?
解决方案
我们将使用Redux库来管理全局应用程序状态。Redux使用与React useReducer函数相同的reducer,但它只使用一个对象管理整个应用程序的状态。此外,Redux还有许多扩展,可以解决常见的编程问题,并能帮你更快地开发和管理应用程序。
首先,我们来安装Redux库:
我们还将安装React Redux库,它将让Redux更容易与React一起使用:
我们将使用Redux创建一个包含购物篮的应用程序(如图3-16所示)。
图3-16:当客户购买某个商品时,应用程序将其添加到购物篮中
如果客户单击Buy按钮,应用程序会将商品添加到购物篮中。如果他们再次单击Buy按钮,篮子中商品的数量就会更新。购物篮将出现在应用程序的多个位置,所以它是可以用Redux来管理的好例子。下面是我们用来管理购物篮的reducer函数:
此处我们创建的是单个reducer。如果应用程序体积变大,你可能会希望将reducer进行相应的拆分,之后再使用combineReducers函数( https://oreil.ly/IVh7x )对拆分的reducer进行合并。
上面的reducer响应了两个类型的动作,分别为buy和clearBasket。buy动作将向购物篮中添加新商品,或者更新现有商品的数量(如果有匹配的productId)。clearBasket动作将购物篮的商品数值重置为空数组。
现在我们有了一个reducer函数,我们将用它来创建一个Redux的store。store将成为共享应用程序状态的中央存储库。为了创建一个store,要将下面这几行代码添加到顶层组件(这里是 App.js ):
store需要在应用程序中保持全局可用,为了做到这一点,我们需要将它注入可能调用它的组件的context中。React Redux库提供了一个名为Provider的组件来将store注入组件的context中:
以下是示例应用程序中的 reducer.js 组件,你可以在本书的GitHub仓库( https://oreil.ly/j90xI )中找到它:
既然我们的组件可以使用store,那么应该如何使用它呢?React Redux允许你通过Hook访问store。如果你想读取全局状态的内容,那么你可以使用useSelector:
useSelector Hook接受一个函数以提取全局状态中的一部分。Selector非常高效,只有当你关心的状态发生变化时,它才会重新渲染组件。
如果你需要提交一个动作到中央store,你可以使用useDispatch Hook:
上述代码将返回一个dispatch函数,你可以使用它向store发送动作:
这两个Hook从当前context提取store。如果你忘记将Provider添加到应用程序中,或者尝试在Provider context之外运行useSelector或useDispatch,则会出现报错,如图3-17所示。
图3-17:如果忘记包含Provider,就会出现此错误
读取和清空整个应用的购物篮的完整Basket组件的代码如下:
为演示向购物篮中添加商品的相关代码,下面是Boots组件的代码实现,它允许客户购买一系列商品:
这两个组件可能出现在组件树中不同的位置,但它们共享同一个Redux store。一旦客户将商品添加到购物篮中,Basket组件将自动更新(如图3-18所示)。
图3-18:Redux-React的Hook确保当用户购买产品时,Basket被重新渲染
讨论
开发人员经常在React框架中使用Redux库。似乎很长一段时间以来,每个React应用程序都默认包含Redux。但实际上,Redux经常被过度使用或不当使用。我们已经看到一些项目甚至禁用本地状态以便用Redux来管理全局状态。我们认为这种做法是错误的。Redux用于集中管理应用程序状态,不适用于简单的组件状态管理。如果你存储的数据只涉及一个组件,或者它的子组件,那么你最好不要把数据存储在Redux中。
但是,如果你的应用程序管理某些全局应用程序状态,那么Redux仍然是首选工具。
你可以从GitHub网站( https://oreil.ly/j90xI )下载本解决方案的源代码。