写点什么

为什么我不再用 Redux 了

  • 2020-08-18
  • 本文字数:2838 字

    阅读完需:约 9 分钟

为什么我不再用Redux了

本文最初发布于 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 实现:


import React, { useEffect } from "react";import { useSelector, useDispatch } from "react-redux";import axios from 'axios';const SET_TODOS = "SET_TODOS";export const rootReducer = (state = { todos: [] }, action) => {  switch (action.type) {    case SET_TODOS:      return { ...state, todos: action.payload };    default:      return state;  }};export const App = () => {  const todos = useSelector((state) => state.todos);  const dispatch = useDispatch();  useEffect(() => {    const fetchPosts = async () => {      const { data } = await axios.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 实现的相同示例:


import React from "react";import { useQuery } from "react-query";import axios from "axios";const fetchTodos = () => {  const { data } = axios.get("/api/todos");  return data;};const App = () => {  const { data } = useQuery("todos", fetchTodos);  return data ? (    <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 资源列表,你可以在这里浏览。

SWR

SWR 在概念上与 React Query 几乎一致。React Query 和 SWR 大约是在同一时间开始开发的,并且以积极的方式相互影响。在 react-query 文档中也对这两个库进行了彻底的比较。


与 React Query 一样,SWR 也有真正可读的文档


在大多数情况下,选择任何一个库都没什么问题。不管它们谁会在不久的将来成为事实规范,从它们中重构都要比 Redux 那堆乱麻要简单许多。

Apollo Client

SWR 和 React Query 专注于 REST API,但如果你在 GraphQL 上需要类似的东西,就可以考虑 Apollo Client。令人欣慰的是,它的语法与 React Query 几乎完全一样。

前端状态呢

一旦你开始使用这些库,就会发现在绝大多数项目中 Redux 都太笨重了。处理完应用程序的数据获取 / 缓存部分后,前端几乎没有全局状态可处理。可以使用 Context 或 useContext+useReducer 处理剩下的少量内容,代替 Redux 的作用。


或者更好的方法是,使用 React 的内置状态作为你的简单前端状态,这样做肯定没问题的。


// clean, beautiful, and simpleconst [state, setState] = useState();
复制代码


我们应该更彻底地分离后端与前端,而不是陷在这种模棱两可的中间状态里。本文提到的这些库代表了我们在单页应用程序中管理状态的方式变革,并且是朝着正确方向迈出的一大步。我期待着看到它们能对 React 社区产生怎样的影响。

英文原文

Why I Quit Redux


2020-08-18 09:114187
用户头像
小智 让所有人认同的文字称不上表达

发布了 408 篇内容, 共 403.0 次阅读, 收获喜欢 1986 次。

关注

评论

发布
暂无评论
发现更多内容

统计代码耗时的工具

Rubble

4月日更 4月月更

消息队列存储消息数据的mysql表设计

五月雨

架构实战营 「架构实战营」

尤达 DDD 领域驱动设计思想课程总结

代廉洁

尤达DDD领域驱动设计思想

架构实战营:模块八作业

刘璐

模块3 作业

KennyQ

消息队列MySQL存储设计

石小天

「架构实战营」

消息队列存储消息数据的MySQL表

Fingal

#架构实战营

模块8-设计消息队列存储消息数据的 MySQL 表格

卡西毛豆静爸

#架构实战营

微信小程序开发设计需要注意的五个点

源字节1号

前端 后端 软件开发 小程序开发

模块八作业:设计消息队列存储消息数据的 MySQL 表格

炎彬

「架构实战营」

Bigdata 作业第七周

Pyel

[Day18]-[动态规划] 打家劫舍3

方勇(gopher)

LeetCode 动态规划 数据结构和算法

Gitlab Java API 使用示例

Java gitlab 4月月更

作业八

Geek_f3e842

架构实战营

市场进展不断,STI 包括ZB等一系列上线预示着什么?

BlockChain先知

性能分析优化的道与术

老张

性能优化 性能分析

你好spring-cloud-kubernetes

程序员欣宸

4月月更

linux之rename命令

入门小站

消息队列数据存储表设计

随欣所遇

架构训练营5期

消息队列存储消息数据的 MySQL 表格

阿卷

架构实战营

多系统信息化实施项目注意事项

秋去冬来春未远

数字化 信息化 系统集成 ERP 多系统

爱讲故事的计算机科学家,和他的分布式系统

多颗糖

都是分布式操作系统,Laxcus和鸿蒙有何不同?

LAXCUS分布式操作系统

分布式计算 分布式存储 集群架构 鸿蒙系统 分布式操作系统

在线ASCII Banner艺术字生成工具

入门小站

工具

GitOps多环境部署问题及解决方案

俞凡

研发效能 gitops

消息队列存储消息数据的 MySQL 表格设计

李大虾

#架构实战营 「架构实战营」

模块8作业

Mr小公熊

浅谈项目中的需求管理

秋去冬来春未远

需求管理 需求分析 需求和问题

开疆作剑,开荒为犁:2022春天,文心大模型走进产业的百花深处

脑极体

【架构学习08】——设计消息队列存储消息数据的 MySQL 表格

tiger

架构实战营

商业分析:SheIn是怎样成功的?

石云升

跨境电商 商业分析 4月月更

为什么我不再用Redux了_大前端_Gabriel Abud_InfoQ精选文章