Redux是React生态系统中的革命性技术。它使我们能够在全局范围内存储不可变数据,并解决了在组件树中prop-drilling的问题。需要在应用程序之间共享不可变数据时,它现在依旧是一种可以方便扩展的优秀工具。
但是,为什么我们非得需要一个全局存储呢?我们的前端应用程序真的那么复杂吗,还是说我们试图用Redux做的事情太多了?
现在,异步获取数据意味着数据必须位于两个位置:前端和后端。我们必须考虑如何在全局范围内以最佳方式存储这些数据,以便它们能对我们的所有组件都可用,同时保持数据缓存以减少网络延迟。现在,前端开发中的很大一部分负担来自于我们的全局存储的维护工作,我们还要确保这些存储不会遭受状态错误、数据非规范化和陈旧数据的困扰。
使用Redux和类似的状态管理库时,大多数人都会遇到的一大问题是,我们会将其视为后端状态的缓存。我们获取数据,通过reducer/action将其添加到存储中,并定期重新获取以确保它是最新的。我们用Redux做的事情太多了,甚至把它看成是解决问题的全面解决方案。
关键在于,我们的前端和后端状态永远不会真正同步,我们最多可以营造一种它们同步的错觉。这是客户端-服务器模型的缺点之一,也是为什么我们需要缓存的原因所在。但是,同步缓存和保持状态是非常复杂的,因此我们不应该像Redux鼓励的那样,从头开始重新创建这个后端状态。
当我们开始在前端重新创建数据库时,后端和前端之间的职责界限很快就变得模糊不清。作为前端开发人员,我们不需要完全了解表及其关系即可创建简单的UI。我们也不必知道如何高水平地标准化我们的数据。这种责任应该落在设计表的那些人(后端开发人员)身上。然后,后端开发人员可以用文档化的API形式为前端开发人员提供抽象。
现在,人们围绕Redux构建了无数的库(redux-observable、redux-saga和redux-thunk等),以帮助我们管理后端数据,每个库都为已经繁琐不已的库又增加了一层复杂性。我相信其中大多数都没有达成目标。有时为了前进。我们需要先退后一步。
我认为有两个库比使用Redux(或类似的状态管理库)存储后端状态要好用很多。
ReactQuery
我已经在自己的多数个人和工作项目中使用ReactQuery几个月了。这个库有一个非常简单的API和几个hooks,用于管理查询(获取数据)和突变(更改数据)。
自从使用ReactQuery之后,我不仅提升了效率,而且最终编写的样板代码比Redux少了9成。我发现自己更容易将注意力集中在前端应用程序的UI/UX上,不会再时刻操心整个后端状态了。
要对比这个库和Redux的话,我们来看这两种方法的一个代码示例。我使用常规JS、ReactHooks和axios实现了一个从服务器获取的简单TODO列表。
首先是Redux实现:
请注意,到这里甚至还没有开始处理重新获取、缓存和无效化,只是加载数据并在加载时将其存储在全局存储中而已。
下面是使用ReactQuery实现的相同示例:
默认情况下,上面的示例包括具有合理默认值的数据重新获取、缓存和过时内容无效化。你可以在全局级别设置缓存配置,然后就可以忘掉它了——一般来说它足以完成你期望的工作。有关其幕后工作机制的更多信息,请通过下方链接查看ReactQuery文档。它有大量的配置选项可用,本文只是介绍了一点皮毛。
现在,无论需要什么数据,你都可以将useQueryhook与你设置的唯一键(在本例中为“todos”)一起使用,并使用异步调用来获取数据。只要函数是异步的,实现就无关紧要——你可以轻松地使用FetchAPI代替Axios。
要更改后端状态时,ReactQuery提供了useMutationhook。
SWR
与ReactQuery一样,SWR也有真正可读的文档。
在大多数情况下,选择任何一个库都没什么问题。不管它们谁会在不久的将来成为事实规范,从它们中重构都要比Redux那堆乱麻要简单许多。
ApolloClient
SWR和ReactQuery专注于RESTAPI,但如果你在GraphQL上需要类似的东西,就可以考虑ApolloClient。令人欣慰的是,它的语法与ReactQuery几乎完全一样。
一旦你开始使用这些库,就会发现在绝大多数项目中Redux都太笨重了。处理完应用程序的数据获取/缓存部分后,前端几乎没有全局状态可处理。可以使用Context或useContext+useReducer处理剩下的少量内容,代替Redux的作用。
或者更好的方法是,使用React的内置状态作为你的简单前端状态,这样做肯定没问题的。
我们应该更彻底地分离后端与前端,而不是陷在这种模棱两可的中间状态里。本文提到的这些库代表了我们在单页应用程序中管理状态的方式变革,并且是朝着正确方向迈出的一大步。我期待着看到它们能对React社区产生怎样的影响。