写点什么

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:057082

评论 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
回复
没有更多了
发现更多内容

Databend Parser 快速入门

Databend

分布式存储技术(下):宽表存储与全文搜索引擎的架构原理、特性、优缺点解析

星环科技

分布式 全文搜索

基于公共信箱的全量消息实现

百度Geek说

大数据 即时通讯 企业号 4 月 PK 榜 公共信箱

大语言模型的本质:会思考的狗、聪明的马和随机鹦鹉

FN0

AIGC 大语言模型

电信及互联网行业数据安全内控审计建设实践 | 盾见

极盾科技

数据安全

分布式存储技术(上):HDFS 与 Ceph的架构原理、特性、优缺点解析

星环科技

hdfs 分布式存储 Ceph

电力行业信息化年会 华为解读“低碳、安全、发展”新思路

YG科技

请查收!一份2023年程序员不得不看的自救提升指南(彩色终极版)

Java你猿哥

Java 面试 JVM 面经

浅谈测试用例设计 | 京东云技术团队

京东科技开发者

测试 测试用例 测试用例设计 企业号 4 月 PK 榜

华为ISDP数字化现场作业,如何助力电力行业安监风险管控?

YG科技

MySQL8.0.32的安装与配置

Java你猿哥

Java MySQL ssm Java工程师

Rust-Shyper:基于 Rust 语言的高可靠、开源嵌入式 Hypervisor

openEuler

Linux rust 操作系统 虚拟机 嵌入式

硬核!阿里P8耗时6月打造的架构师速成手册,颠覆你对架构师的认知

Java你猿哥

架构 分布式 ssm 软件架构 架构师

企业数据平台建设的基石:构建统一的数据存算能力

星环科技

存算能力

如何创造数据资产价值?如何对内赋能业务运营,对外创造市场价值?

星环科技

数据资产 数据要素流通

这一秒,困扰了程序员 50 年!

Java你猿哥

Java 程序员 ssm 计算机

15个值得收藏的数据可视化开源工具

2D3D前端可视化开发

数据可视化 数据可视化工具 前端数据可视化 数据可视化设计 数据可视化软件

聊聊「低代码」的实践之路

Java 架构 低代码

如何在微服务下保证事务的一致性 | 京东云技术团队

京东科技开发者

架构 微服务 事务 一致性 企业号 4 月 PK 榜

分布式技术剖析

星环科技

分布式

竞争焦点转向数智底座 用友能否再引领

用友BIP

用友iuap 用友技术大会 升级企业数智化底座

构建系列之webpack窥探上

江湖修行

前端 Web webpack cli 构建

企业如何两步实现数据资产化?

星环科技

数据资产化

统一、飞鹤等快消龙头企业,如何抓住未来10年数智化的机遇?

用友BIP

用友iuap 用友技术大会 快消行业

戴尔科技园动力计划,携手中南高科赋能中小企业数字化转型

科技热闻

关于聚合根,领域事件的那点事---深入浅出理解DDD | 京东云技术团队

京东科技开发者

DDD 企业号 4 月 PK 榜 聚合根 领域事件

字节二面:HashMap线程不安全体现在哪里?

Java你猿哥

Java 线程 ssm 架构师 HashMap底层原理

用友iuap 让企业数智化能力深入、让业务价值浅出

用友BIP

用友 用友iuap 用友技术大会 数智底座

【专栏 03】数据仓库、数据集市、数据湖,你的企业更适合哪种数据管理架构?

星环科技

数据架构

校企共建|阿里云与西安电子科技大学人才培养交流会顺利举行

云布道师

校企合作

从入门到精通,超详细的程序员Java学习路线指南

Java你猿哥

Java 数据库 Web ssm 死磕 Java 基础

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