【QCon】精华内容上线92%,全面覆盖“人工智能+”的典型案例!>>> 了解详情
写点什么

React Hooks 会取代 Redux 吗?

  • 2019-07-29
  • 本文字数:6051 字

    阅读完需:约 20 分钟

React Hooks会取代Redux吗?

自从 React 引入了 Hooks API 以来,关于 React Hooks 是否会取代 Redux 的讨论也越来越多了。本文作者认为 React Hooks 不能取代 Redux,并阐述了该观点的理由。


在我看来,Hooks 和 Redux 之间几乎没什么重叠的使用场景。Hooks 没那么神奇,没有什么全新的状态功能;它只是增强了 React 已有 API 本来就能做的那些事情而已。但 hooks API 提升了原生 React 状态 API 的可用性,而且它比被它取代的 class 模型更简单,所以有了 hooks 后我在合适的场景中使用组件状态的频率多了很多。


为了阐明我的观点,首先我们来探讨为什么 Redux 会是首选。

Redux 是什么?

Redux 是一个可预测的状态管理库和架构,可以与 React 轻松集成。


Redux 的主要优点有:



  • 确定性状态视图(与纯组件结合时启用确定性视图呈现)。



  • 事务状态。



  • 状态管理与 I/O 和副作用隔离。



  • 应用程序状态使用单一信息源。



  • 在不同组件之间轻松共享状态。



  • 事务遥测(自动记录操作对象)。



  • 跟踪调试。



总的来说,Redux 提供了强大的代码组织和调试能力。开发者可以使用它构建更易维护的代码,并且在出现故障时更快找到问题的根源。

React Hooks 是什么?

开发者只要用 React Hooks 就能使用状态和 React 生命周期中的众多功能,无需再用 class 和 React 组件生命周期方法。React Hooks 是 React 16.8 中首次引入的。


React Hooks 的主要优点有:



  • 无需 class 也能在组件生命周期中使用状态和 Hooks。



  • 将相关逻辑集中起来放进组件,而不是把它们分散到各个生命周期方法中。



  • 与组件实现(例如呈现prop模式)独立的共享可复用行为。



请注意,React Hooks 的这些吸引人的优点和 Redux 并没有重叠。你应该使用 React Hooks 来获得确定的状态更新,但这也是 React 本身一直就有的功能,而 Redux 的确定性状态模型与它配合的很好。这就是 React 提供确定性视图呈现的手段,而且也是促使 React 诞生的动力之一。


现在有了react-redux hooks API 和 React 的 useReducer hooks 等工具,你用不着在 React hooks 和 Redux 之间二选一了。鱼与熊掌可以兼得,你应该两个都用,搭配起来干活儿。

Hooks 取代了哪些技术?

自从 Hooks API 诞生之后,我不再使用的技术有:


  • class 组件。

  • 呈现 prop 模式。

Hooks 不能取代的技术有哪些?

我还在经常使用的技术有:



  • Redux,原因如前所述。



  • 高阶组件(Higher Order Components),这些组件负责构成由全部或多数应用程序视图共享的交叉关注点(cross-cutting concerns,也称横切关注点),例如 Redux 提供程序、公共布局提供程序、配置提供程序、身份验证/授权程序、i18n 等。



  • 容器和显示组件之间的隔离,以实现更好的模块化,改善可测试性,更容易分离视觉效果和纯逻辑。


何时使用 Hooks

不是说所有应用或组件都得用 Redux 的。如果你的应用只有单个视图,不保存或加载状态,并且没有异步 I/O,那么引入 Redux 只会让应用变复杂而已。


同样,如果你的组件:



  • 不使用网络。



  • 不保存或加载状态。



  • 不与其他非子组件共享状态。



  • 需要一些临时的本地组件状态。



那么你就很适合使用 React 的内置组件状态模型。在这些情况下 React hooks 就能大显身手了。例如,下面这个表单使用了 React 中的本地组件状态 useState hook。


import React, { useState } from 'react';import t from 'prop-types';import TextField, { Input } from '@material/react-text-field';const noop = () => {};const Holder = ({  itemPrice = 175,  name = '',  email = '',  id = '',  removeHolder = noop,  showRemoveButton = false,}) => {  const [nameInput, setName] = useState(name);  const [emailInput, setEmail] = useState(email);const setter = set => e => {    const { target } = e;    const { value } = target;    set(value);  };return (    <div className="row">      <div className="holder">        <div className="holder-name">          <TextField label="Name">            <Input value={nameInput} onChange={setter(setName)} required />          </TextField>        </div>        <div className="holder-email">          <TextField label="Email">            <Input              value={emailInput}              onChange={setter(setEmail)}              type="email"              required            />          </TextField>        </div>        {showRemoveButton && (          <button            className="remove-holder"            aria-label="Remove membership"            onClick={e => {              e.preventDefault();              removeHolder(id);            }}          >            &times;          </button>        )}      </div>      <div className="line-item-price">${itemPrice}</div>      <style jsx>{cssHere}</style>    </div>  );};Holder.propTypes = {  name: t.string,  setName: t.func,  email: t.string,  setEmail: t.func,  itemPrice: t.number,  id: t.string,  removeHolder: t.func,  showRemoveButton: t.bool,};export default Holder;
复制代码


这里的代码使用 useState 来跟踪要填入名称和电子邮件的临时表单的输入状态:


const [nameInput, setName] = useState(name);const [emailInput, setEmail] = useState(email);
复制代码


你可能会注意到还有一个 removeHolder 动作创建者,加入来自 Redux 的 prop。各种方法可以混合搭配。


解决这类问题时都可以使用本地组件状态,但我首先想到的办法可能就是将它塞到 R​​edux 里面,并把状态从 prop 中拿出来吧。


使用组件状态意味着要使用 class 组件,还要使用 class 实例属性语法(或 constructor 函数)设置初始状态,等等——就为了不用 Redux,麻烦事也太多了。Redux 有一些管理表单状态的即插即用工具,因此我不必担心临时表单状态渗入我的业务逻辑。


我新开发的应用几乎都用到了 Redux,以前做选择是很简单的:几乎所有项目都用 Redux 就行了!


现在这个选择还是很简单:


组件状态用组件状态,应用状态用 Redux。

何时使用 Redux

另一个常见问题是“你应该把所有东西放在 Redux 中吗?不这样做的话,它会不会破坏跟踪调试呢?“


答案是不会的,因为应用程序中有很多状态是临时的,而且过于粗糙,无法为日志遥测或跟踪调试提供比较有用的信息。除非你正在构建一个实时协作的编辑器,否则你可能并不需要将所有用户按键或鼠标移动行为放进 Redux 状态里。当你向 Redux 状态添加内容时,加进来的是一个抽象层以及随之而来的复杂度。


换句话说,你大可放心使用 Redux,但是每次用到它时应该有合适的理由。


如果你的组件有以下需求,那么 Redux 会很有用:


  • 使用网络之类的 I/O 或设备 API。

  • 保存或加载状态。

  • 与非子组件共享其状态。

  • 需要与应用程序的其他部分共享业务逻辑或数据处理过程


下面这个例子来自TDDDay应用


import React from 'react';import { useDispatch, useSelector } from 'react-redux';import { compose } from 'ramda';import page from '../../hocs/page.js';import Purchase from './purchase-component.js';import { addHolder, removeHolder, getHolders } from './purchase-reducer.js';const PurchasePage = () => {  // You can use these instead of  // mapStateToProps and mapDispatchToProps  const dispatch = useDispatch();  const holders = useSelector(getHolders);const props = {    // Use function composition to compose action creators    // with dispatch. See "Composing Software" for details.    addHolder: compose(      dispatch,      addHolder    ),    removeHolder: compose(      dispatch,      removeHolder    ),    holders,  };return <Purchase {...props} />;};// `page` is a Higher Order Component composed of many// other higher order components using function composition.export default page(PurchasePage);
复制代码


这个组件并不处理 DOM。这是一个展示组件。它使用 React-Redux hooks API 连接到 Redux 上。


之所以它会用到 Redux 是因为 UI 的其他部分需要这个表单的数据,另外当购买流程完成后,我们需要将数据保存到数据库中。


它的状态不是本地化到单个组件中,而是在组件之间共享的;状态是持久而非临时的,并且它可能跨越多个页面视图或会话。除非你在 React API 之上构建自己的状态容器库,否则这些都是本地组件状态无法实现的效果——但状态容器库比 Redux 要复杂得多。


将来,React 的suspense API或许能用来保存和加载状态。等到 suspense API 发布后,我们就能知道它能否取代 Redux 中的保存/加载模式了。Redux 允许我们干净地将副作用与其他组件逻辑分离开来,不需要我们模拟 I/O 服务。(相比 thunk,我更喜欢使用redux-saga的原因就是后者的隔离功能)。为了在这个用例上追赶 Redux 的脚步,React 的 API 需要提供副作用隔离功能。

Redux 是一种架构

Redux 与状态管理库有着很大区别。它本质上也是Flux架构的一个子集,更关注状态变化的过程。我在另一篇博文中详细介绍了Redux架构


当我需要复杂的组件状态但又用不着 Redux 库时,我经常会使用 redux 风格的 Reducer。我还会使用 Redux 风格的操作(甚至是 Redux 工具,如 Autodux 和 redux-saga 等)调度 Node 应用中的操作,这样就无需导入 Redux 库了。


与库相比,Redux 向来更接近一种架构和非强制性的约定(convention)。事实上,Redux 的基本实现只需要几十行代码就能复现。


这也是 Redux 的一大好处。如果你想多用一些本地组件状态和 hook API,不想把所有内容都塞到 Redux 里,那也完全没问题。


React 提供了一个 useReducer hool,可以用它接入你的 Redux 风格的 Reducer。这对不常见的状态逻辑、依赖状态等内容非常有用。如果你的用例是要将临时状态装入单个组件,则可以使用 Redux 架构,但要用 useReducer hook 取代 Redux 来管理状态。


如果你随后需要维持或分享这个状态,那么大部分工作其实已经做完了。剩下的就是连接组件并将 Reducer 添加到 Redux 存储了。

答疑

“如果不把所有内容都装进 Redux,会损害确定性吗?”


不会的。实际上 Redux 也没有强制执行确定性机制。能这样做的是约定。如果你希望 Redux 状态是确定性的,请使用纯函数。如果你希望临时组件状态是确定性的,也要用纯函数。


“难道你不需要 Redux 作为单一信息源吗?”


单一信息源原则并不是说你要让所有状态有同一个来源。相反,它意味着对于每一个状态来说,该状态应该有一个单一信息源。你可以有许多不同的状态,每个状态都有自己的单一信息源。


这意味着你可以选择让哪些内容进入 Redux 的内容或组件状态。你还可以从其他来源获取状态,例如从浏览器 API 获取当前位置 href。


Redux 作为应用程序状态的单一信息源是很好用的,但如果组件状态本地化为单个组件,并且仅在一处使用,那么根据定义它已经为该状态提供了单一信息源:也就是 React 组件状态。


如果你将某些内容放入 Redux 状态,则应始终从 Redux 状态读取它们。对于 Redux 中的所有状态来说,Redux 应该是它们的单一信息源。


需要的话可以将所有内容都放在 Redux 中。这可能会对需要频繁更新的状态或具有大量依赖状态的组件产生性能影响。一般来说你用不着考虑性能瓶颈,但真遇到性能问题时就分别尝试一下调节参数和 RAIL 性能模型两种方法,看看有没有效果。


“我应该使用 react-redux 连接还是 hooks API?”


具体情况具体分析。connect 创建一个可复用的高阶组件,而 hooks API 则是针对单个组件的集成优化的。你是要将同样的存储 prop 连接到其他组件吗?那就用 connect。否则我更喜欢 hooks API 的读取方式。例如,假设你有一个处理用户操作权限授权的组件:


import { connect } from 'react-redux';import RequiresPermission from './requires-permission-component';import { userHasPermission } from '../../features/user-profile/user-profile-reducer';import curry from 'lodash/fp/curry';
const requiresPermission = curry( (NotPermittedComponent, { permission }, PermittedComponent) => { const mapStateToProps = state => ({ NotPermittedComponent, PermittedComponent, isPermitted: userHasPermission(state, permission), });
return connect(mapStateToProps)(RequiresPermission); },);
export default requiresPermission;
复制代码


现在,如果你有一堆管理员视图都需要管理员权限,那么你可以创建一个高阶组件,根据这个权限需求为它们构造所需的交叉关注点:


import NextError from 'next/error';import compose from 'lodash/fp/compose';import React from 'react';import requiresPermission from '../requires-permission';import withFeatures from '../with-features';import withAuth from '../with-auth';import withEnv from '../with-env';import withLoader from '../with-loader';import withLayout from '../with-layout';
export default compose( withEnv, withAuth, withLoader, withLayout(), withFeatures, requiresPermission(() => <NextError statusCode={404} />, { permission: 'admin', }),);
复制代码


要使用它时:


import compose from 'lodash/fp/compose';import adminPage from '../HOCs/admin-page';import AdminIndex from '../features/admin-index/admin-index-component.js';
export default adminPage(AdminIndex);
复制代码


高阶组件 API 对于这类用例来说是很方便的,它实际上比 hooks API 更简洁(需要的代码更少),但是为了读取 connect API,你必须记住它需要 mapStateToProps 作为第一个参数,用 mapDispatchToProps 作为第二个参数;你大概知道它可以读取函数或对象字面量,也知道这些行为的差异。你还需要记住它是 curry 过的,但不是自动 curry 的。


换句话说,connect API 的代码更简洁,但它不是特别易读,不那么友好。如果我不需要为其他组件复用连接,我更喜欢可读性好得多的 hooks API,多打一些代码也无妨。


“如果单例模式是反模式,而 Redux 是单例模式,那么 Redux 不就是反模式吗?”


并不是。单例模式是一种代码形式,可以表明共享的可变状态,这是真正的反模式。Redux 通过封装防止了共享可变状态(你不应该直接在 reducer 之外改变应用状态,而要让 Redux 处理状态更新)和消息传递(只有调度的动作对象才能触发状态更新)。

更多内容

可在 EricElliottJS.com 上了解有关 React 和 Redux 的更多信息。网站上有很多示例和视频,深入讨论本文涉及到的代码示例、函数组合等内容。


在这里了解如何对 React 组件进行单元测试:https://medium.com/javascript-scene/unit-testing-react-components-aeda9a44aae2?source=post_page;


至于测试这方面的内容,可以在 TDDDay.com 上了解测试驱动开发(TDD)。


原文链接https://medium.com/javascript-scene/do-react-hooks-replace-redux-210bab340672


2019-07-29 18:057072

评论 2 条评论

发布
用户头像
https://stackblitz.com/edit/hook-setup?file=index.js
hook, renderProps, class 3种写法高度统一,任君切换
setup, computed, watch, sync 等特性,赋能react更多玩法
powered by concent
2019-08-15 10:20
回复
用户头像
但我还是要说, 不要为了使用 Redux 而使用 Redux.
2019-07-30 15:45
回复
没有更多了
发现更多内容

Ulysses for Mac(优秀的markdown写作软件)v34.2中文直装版

影影绰绰一往直前

Acrobat Pro DC 2023直装破解版/便携版下载安装 及acrobat pro dc 2023新增功能介绍

Rose

pdf编辑器 Acrobat Pro DC 2023下载 Acrobat Pro DC便携版

《2023 IT行业项目管理调查报告》新鲜出炉!助力IT行业持续稳步发展

禅道项目管理

项目管理 IT 调查报告 行业趋势

Perfectly Clear Workbench for Mac(智能图像清晰修复软件)v4.6.0.2645永久激活版

影影绰绰一往直前

一文让你简单了解跨境电商需要购买堡垒机的几大原因

行云管家

网络安全 电商 数据安全 堡垒机 跨境电商

【亲测有效】axureRP9授权密钥 Mac/Windows

Rose

两场CVPR 2024 研讨会,一探大模型闭环真身

飞桨PaddlePaddle

百度 BAIDU 百度飞桨 文心一言 文心大模型

Redis Desktop Manager for mac中文版- Redis 的跨平台 GUI 管理工具

Rose

以科技助力非遗文化传承,华为音乐空间音频让传统民乐重焕“新声”

最新动态

掌静脉识别技术助力城轨交通:英特尔携手生态伙伴引领行业转型升级

E科讯

阿里云 EMR Serverless Spark 版免费邀测中

阿里云大数据AI技术

大数据 spark 数据开发

autocad2022序列号和密钥 autocad2022中文版详细图文安装教程

Rose

AutoCAD2022序列号 autocad2022秘钥 autocad2022破解版 cad2022Mac版

Doppler for mac(音乐播放器)v2.1.20激活版

影影绰绰一往直前

【永久可用】IntelliJ IDEA 2023全版本激活码 适用于macOS/Windows

Rose

intellij idea 2023激活码 intellij idea 2023中文版

React memo的原理、实践与思考

京东科技开发者

日本股票盘搭建

GangguHK

虚拟主播+电商直播将重塑众多行业格局!

青否数字人

数字人

新手程序员怎么在复杂的代码中寻找bug ?

小魏写代码

如何安装VMware Fusion Pro 13虚拟机?VM虚拟机密钥版详细安装教程

Rose

VMware Fusion Pro 13 VMware Fusion激活秘钥 VMware Fusion 虚拟机 VM安装教程

SecureCRT for mac如何进行远程连接?含SecureCRT mac永久激活秘钥

Rose

SecureCRT激活 SecureCRT Mac破解版 SecureCRT 安装教程 SecureCRT怎么用 远程管理

基于微信小程序的CMS内容管理系统开发笔记

CC同学

【稳定性】稳定性建设之依赖设计

京东科技开发者

Mp3tag for Mac(音频标签编辑器)v1.8.18激活版

影影绰绰一往直前

Photoshop 2024最新中文直装版 含ps2024激活补丁 Mac/win

Rose

亮数据代理IP轻松解决爬虫数据采集痛点

袁袁袁袁满

定时任务优化总结(从半个小时优化到秒级)

京东科技开发者

IPQ9554 vs IPQ8074: Which Chipset Reigns Supreme in the World of Networking and Connectivity?

wifi6-yiyi

5G WiFi7 6G

EdgeView 4 for Mac(快速图像查看器)v4.6.2激活版

影影绰绰一往直前

Java 8 内存管理原理解析及内存故障排查实践

vivo互联网技术

实现原理 回收算法 性能诊断及优化技巧

程序员担心被 AI 替代?听听这两位 CEO 怎么说

极狐GitLab

程序员 AI 大模型

Beyond Compare 4 for Mac永久激活秘钥 Beyond compare 4中文汉化安装包

Rose

React Hooks会取代Redux吗?_语言 & 开发_Eric Elliott_InfoQ精选文章