AICon 上海站|90%日程已就绪,解锁Al未来! 了解详情
写点什么

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

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

关注

评论

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

阿里P8重磅总结:看完别说不会了哦,SpringBoot「完结篇」

比伯

Java 编程 程序人生 计算机 架构】

Go Functions

escray

学习 极客时间 Go 语言 4月日更

因为这几个TypeScript代码的坏习惯,同事被罚了500块

华为云开发者联盟

typescript 运算符 代码 null strict

架构实战营模块二作业

日照时间长

架构实战营

第 0 期架构训练营模块 2 作业

架构实战营

2021互联网大厂高频面试专题500道:并发编程/Spring/MyBatis(附答案解析)

比伯

Java 编程 架构 程序人生 计算机

LitmusChaos: K8s上的混沌工程框架

混沌工程实践

k8s 混沌工程 litmuschaos 实践框架 故障实验库

安卓内存监控悬浮窗,2021Android面试心得,全套教学资料

欢喜学安卓

android 程序员 面试 移动开发

数据脱敏:数仓安全隐私保护见真招儿

华为云开发者联盟

数据仓库 加密 隐私保护 GaussDB(DWS) 数据脱敏

第十一周总结

热乎的6个Notion使用技巧,学不会算我输。

彭宏豪95

效率 Notion 笔记 4月日更

安卓rxjava合并多个请求,我的阿里手淘面试经历分享,面试必会

欢喜学安卓

android 程序员 面试 移动开发

external-provisioner源码分析(2)-main方法与Leader选举分析

良凯尔

Kubernetes 源码分析 Ceph CSI

新同学与老司机

小天同学

职场成长 工作体会 经验总结 4月日更 新同学

external-provisioner源码分析(3)-组件启动参数分析

良凯尔

Kubernetes 源码分析 Ceph CSI

架构实战营 模块二作业

fazinter

架构实战营

Golang Map 和字符串

escray

学习 极客时间 Go 语言 4月日更

python 函数详解

若尘

函数编程 函数

技术实践丨列存表并发更新时的锁等待问题原理

华为云开发者联盟

事务 update 元组 列存表

边缘计算是流行词还是风口?开发者怎样选开源项目?

华为云开发者联盟

开源 开发者 5G 边缘计算 EdgeGallery 社区

模块二作业

求索

架构实战营

架構實戰營 - 模塊 2 作業

Frank Yang

架构实战营

k8s通过ceph-csi接入存储的概要分析

良凯尔

Kubernetes 源码分析 Ceph CSI

external-provisioner源码分析(1)-主体处理逻辑分析

良凯尔

Kubernetes 源码分析 Ceph CSI

Play with Go

Rayjun

教程 Go 语言

基于crudapi增删改查接口后端Java SDK二次开发之环境搭建(一)

crudapi

Java API sdk crud crudapi

kubernetes ceph-csi分析-目录导航

良凯尔

Kubernetes 源码分析 Ceph CSI Kubernetes Plugin

这才是大数据的正确打开方式

华为云开发者联盟

大数据 数据仓库 云原生 数据治理 灾备

Python-Net编程

若尘

Python 网络编程 net

Spring Bean创建过程的Hook

邱学喆

BeanPostProcessor @Autowired注入原理 @Resource注入原理 @Value注入原理

MySQL存储过程的异常处理

Sakura

4月日更

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