写点什么

为什么我不再用 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:114261
用户头像
小智 让所有人认同的文字称不上表达

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

关注

评论

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

第8周作业

小胖子

应用程序研发之网络 - Http

superman

读完《云原生架构白皮书》,我们来谈谈开放应用模型(OAM)

郭旭东

Kubernetes 云原生 OMA

MySQL主从复制详解

Simon

MySQL 主从复制

百万并发「零拷贝」技术系列之Linux实现

码农神说

Java 架构 零拷贝

简易web性能工具

鲁米

JDK1.8新特性(六):Stream的终极操作,轻松解决集合分组、汇总等复杂操作

xcbeyond

stream 集合 JDK1.8 Collections JDK1.8新特性

Jenkins 多分支项目过滤及 when 的高级用法

jerry.mei

DevOps 运维 自动化 jenkins CI/CD

计算机的时钟(二):Lamport逻辑时钟

ElvinYang

应用程序研发之网络-网络编程模型

superman

5万字长文:Stream和Lambda表达式最佳实践-附PDF下载

程序那些事

Java jdk Lambda stream

作业1

武鹏

LeetCode题解: 206. 反转链表,JavaScript,容易理解的递归解释,详细注释

Lee Chen

大前端 LeetCode

C++编译过程 宏 内联和静态变量

正向成长

程序的机器级表示-访问数据

引花眠

从零开始写一个迷你版的Tomcat

简爱W

耦合层:撮合物联网的理论与实践牵手的“月老”

华为云开发者联盟

AI 物联网 IoT 低耦合 华为云

初识进程coredump(以中间件为例)异常宕机

清康

ARTS 06 - Jenkins 多分支项目过滤及 when 的高级用法

jerry.mei

学习 算法 ARTS 打卡计划 CI/CD ARTS活动

门面效应 - 拒绝别人会产生愧疚吗?

石云升

心理学 门面效应 留面子效应

Flink 使用大状态时的一点优化

Apache Flink

flink RocksDB

云图说|“真人?机器?傻傻分不清!” WAF Bot管理,带你慧眼辨“精”!

华为云开发者联盟

bootstrap 搜索引擎 安全 防火墙 华为云

应用程序研发之网络-分层模型

superman

第8周-作业2

seng man

轻松应对并发问题,Newbe.Claptrap 框架中 State 和 Event 应该如何理解?

newbe36524

分布式 微服务 架构设计 .net core ASP.NET Core

Java 8 中的函数式接口

陈皮

架构师训练营第八周课后总结

Cloud.

ARTS打卡 第9周

引花眠

ARTS 打卡计划

MySQL 百万级数据量分页查询方法及其优化

xcbeyond

SQL优化 数据库优化

第8周-作业1

seng man

封装element-ui表格,我是这样做的

前端有的玩

Java Vue Element 封装

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