InfoQ|为什么我不再用Redux了

作者 | Gabriel Abud 译者 | 王强 策划 | 小智
本文最初发布于 DEV 网站 , 经原作者授权由 InfoQ 中文站翻译并分享 。
Redux 是 React 生态系统中的革命性技术 。 它使我们能够在全局范围内存储不可变数据 , 并解决了在组件树中 prop-drilling 的问题 。 需要在应用程序之间共享不可变数据时 , 它现在依旧是一种可以方便扩展的优秀工具 。
但是 , 为什么我们非得需要一个全局存储呢?我们的前端应用程序真的那么复杂吗 , 还是说我们试图用 Redux 做的事情太多了?
单页应用程序的问题React 这样的单页应用程序(SPA)的出现为我们开发 Web 应用程序的方式带来了许多变化 。 它将我们的后端与前端代码分离开来 , 使我们能够专心一致并分离出关注点 。 围绕状态 , 它还引入了很多复杂性 。
现在 , 异步获取数据意味着数据必须位于两个位置:前端和后端 。 我们必须考虑如何在全局范围内以最佳方式存储这些数据 , 以便它们能对我们的所有组件都可用 , 同时保持数据缓存以减少网络延迟 。 现在 , 前端开发中的很大一部分负担来自于我们的全局存储的维护工作 , 我们还要确保这些存储不会遭受状态错误、数据非规范化和陈旧数据的困扰 。
Redux 不是缓存使用 Redux 和类似的状态管理库时 , 大多数人都会遇到的一大问题是 , 我们会将其视为后端状态的缓存 。 我们获取数据 , 通过 reducer/action 将其添加到存储中 , 并定期重新获取以确保它是最新的 。 我们用 Redux 做的事情太多了 , 甚至把它看成是解决问题的全面解决方案 。
关键在于 , 我们的前端和后端状态永远不会真正同步 , 我们最多可以营造一种它们同步的错觉 。 这是客户端 - 服务器模型的缺点之一 , 也是为什么我们需要缓存的原因所在 。 但是 , 同步缓存和保持状态是非常复杂的 , 因此我们不应该像 Redux 鼓励的那样 , 从头开始重新创建这个后端状态 。
当我们开始在前端重新创建数据库时 , 后端和前端之间的职责界限很快就变得模糊不清 。 作为前端开发人员 , 我们不需要完全了解表及其关系即可创建简单的 UI 。 我们也不必知道如何高水平地标准化我们的数据 。 这种责任应该落在设计表的那些人(后端开发人员)身上 。 然后 , 后端开发人员可以用文档化的 API 形式为前端开发人员提供抽象 。
现在 , 人们围绕 Redux 构建了无数的库(redux-observable、redux-saga 和 redux-thunk 等) , 以帮助我们管理后端数据 , 每个库都为已经繁琐不已的库又增加了一层复杂性 。 我相信其中大多数都没有达成目标 。 有时为了前进 。 我们需要先退后一步 。
如果我们不再在前端代码中管理后端状态 , 而只是将其视为需要定期更新的缓存会怎么样呢?将前端视为从缓存读取内容的简单显示层后 , 我们的代码就会变得更加易用 , 并且更适合纯前端开发人员阅读 。 我们获得了分离关注点的所有好处 , 同时避开了构建 SPA 的大部分缺点 。
后端状态的更简单方法我认为有两个库比使用 Redux(或类似的状态管理库)存储后端状态要好用很多 。
React Query我已经在自己的多数个人和工作项目中使用 React Query 几个月了 。 这个库有一个非常简单的 API 和几个 hooks , 用于管理查询(获取数据)和突变(更改数据) 。
自从使用 React Query 之后 , 我不仅提升了效率 , 而且最终编写的样板代码比 Redux 少了 9 成 。 我发现自己更容易将注意力集中在前端应用程序的 UI/UX 上 , 不会再时刻操心整个后端状态了 。
要对比这个库和 Redux 的话 , 我们来看这两种方法的一个代码示例 。 我使用常规 JS、React Hooks 和 axios 实现了一个从服务器获取的简单 TODO 列表 。
首先是 Redux 实现: importReact, { useEffect }from"react"; import{ useSelector, useDispatch }from"react-redux"; importaxiosfrom'axios'; caseSET_TODOS: return{ ...state,todos: action.payload }; default: returnstate; } }; exportconstApp ==>{ consttodos = useSelector((state) =>state.todos); constdispatch = useDispatch; useEffect(=>{ constfetchPosts =async => { const{ data } =awaitaxios.get("/api/todos"); dispatch({ type: SET_TODOS, payload: data} ); }; fetchPosts; }, ); return( <ul>{todos.length > 0 && todos.map((todo) =><li>{todo.text}</li>)}</ul> ); }; 请注意 , 到这里甚至还没有开始处理重新获取、缓存和无效化 , 只是加载数据并在加载时将其存储在全局存储中而已 。
下面是使用 React Query 实现的相同示例: importReactfrom"react"; import{ useQuery }from"react-query"; importaxiosfrom"axios"; constfetchTodos ==>{ const{ data } = axios.get("/api/todos"); returndata; }; constApp ==>{ const{ data } = useQuery("todos", fetchTodos); returndata ? ( <ul>{data.length > 0 && data.map((todo) =><li>{todo.text}</li>)}</ul> ) :null; }; 默认情况下 , 上面的示例包括具有合理默认值的数据重新获取、缓存和过时内容无效化 。 你可以在全局级别设置缓存配置 , 然后就可以忘掉它了——一般来说它足以完成你期望的工作 。 有关其幕后工作机制的更多信息 , 请通过下方链接查看 React Query 文档 。 它有大量的配置选项可用 , 本文只是介绍了一点皮毛 。
现在 , 无论需要什么数据 , 你都可以将 useQuery hook 与你设置的唯一键(在本例中为“todos”)一起使用 , 并使用异步调用来获取数据 。 只要函数是异步的 , 实现就无关紧要——你可以轻松地使用 Fetch API 代替 Axios 。
要更改后端状态时 , React Query 提供了 useMutation hook 。
我还写了一份精选的 React Query 资源列表 , 你可以在这里浏览 。
SWRSWR 在概念上与 React Query 几乎一致 。 React Query 和 SWR 大约是在同一时间开始开发的 , 并且以积极的方式相互影响 。 在 react-query 文档中也对这两个库进行了彻底的比较 。
与 React Query 一样 , SWR 也有真正可读的文档 。
在大多数情况下 , 选择任何一个库都没什么问题 。 不管它们谁会在不久的将来成为事实规范 , 从它们中重构都要比 Redux 那堆乱麻要简单许多 。
Apollo ClientSWR 和 React Query 专注于 REST API , 但如果你在 GraphQL 上需要类似的东西 , 就可以考虑 Apollo Client 。 令人欣慰的是 , 它的语法与 React Query 几乎完全一样 。
前端状态呢一旦你开始使用这些库 , 就会发现在绝大多数项目中 Redux 都太笨重了 。 处理完应用程序的数据获取 / 缓存部分后 , 前端几乎没有全局状态可处理 。 可以使用 Context 或 useContext+useReducer 处理剩下的少量内容 , 代替 Redux 的作用 。
或者更好的方法是 , 使用 React 的内置状态作为你的简单前端状态 , 这样做肯定没问题的 。// clean, beautiful, and simple const [state, setState] = useState;我们应该更彻底地分离后端与前端 , 而不是陷在这种模棱两可的中间状态里 。 本文提到的这些库代表了我们在单页应用程序中管理状态的方式变革 , 并且是朝着正确方向迈出的一大步 。 我期待着看到它们能对 React 社区产生怎样的影响 。
参考阅读:
InfoQ 写作平台欢迎所有热爱技术、热爱创作、热爱分享的内容创作者入驻!
还有更多超值活动等你来!
填写申请 , 成为作者
开启你的创作之路吧~
InfoQ|为什么我不再用Redux了
本文插图
【InfoQ|为什么我不再用Redux了】点个在看少个 bug ??


    推荐阅读