Redux 中间件中的核心概念:Thunk、Saga、Promise

面试官:小伙子,你的代码为什么这么丝滑?

在前端开发中,Redux已经成为了必不可少的技术之一。其状态管理和组件化的模式为我们提供了非常强大的能力处理复杂的业务逻辑。然而,在我们使用该库的时候,我们经常需要引入一些中间件。本文将会介绍 Redux 中间件中的核心概念:Thunk、Saga、Promise等,并详细讲解其用法及注意事项。

Redux Middleware

在 Redux 中,中间件是由一系列的 $store.dispatch() 调用组成,这些调用通过一些定制化的函数来改变 Redux store 的行为。每个中间件都可以访问 store 的 dispatchgetState 方法,同时,如果需要赋予 extra arguments(额外的参数)可以复用 compositional currying 技巧来触达每一个经过的中间件函数。同时中间件也会启用其自身的 effects 链,如异步的 actions。中间件允许你去捕捉和解释通过 dispatch 发布的 actions,和当它们到达到 reducer 之前拦截它们。

基本的中间件实现

像这样的一个基础中间件,非常容易写:

----- ------------ - ----- -- ---- -- ------ -- -
  -- ---------------------------
  ------ -------------
--

这就是一个标准的中间件,在代码中可以添加我们需要的行为,然后我们可以使用 applyMiddleware() 方法调用它们。让我们详细了解在 Redux 中如何使用中间件。

如何使用中间件

Redux 官方库提供了一个名为 applyMiddleware() 的方法,用于将中间件应用到 store 上。applyMiddleware() 接受多个中间件作为参数,而每个中间件是一个函数。

例如,使用 redux-thunk 中间件将看起来像这样:

------ - ------------ --------------- - ---- --------
------ --------------- ---- --------------
------ ----------- ---- -------------

----- ----- - ------------
  ------------
  --------------------------------
--

在这个例子中,我们将 thunkMiddleware 作为 applyMiddleware() 的参数。这使得 Redux 的 store 能够理解下面的 action creator 函数:

-------- --------------------- -
  ------ ------------------ -
    ----------------------------------
    ------ ---------------------------------------------------
      ------
        -------- -- ----------------
        ----- -- --------------- ----- ----------- ------
      -
      ---------- --
        -------------------------------- ------
      --
  --
-

这个函数说了当我们调用 fetchPosts() 时,将发起一个针对 subreddit 的网络 API 请求。dispatch(requestPosts(subreddit)) 等价于 store.dispatch(requestPosts(subreddit)),那么这个 action 将会交由 requestPosts 的 reducer 来处理。在此期间,我们可以渲染一个 UI 以使用户知晓我们正在加载数据。当 Promise 链在网络请求返回值后处理接续时,就会调用 dispatch(receivePosts(subreddit, json)) 函数,它等价于:store.dispatch(receivePosts(subreddit, json))

至此,我们完成了一个最简单的中间件示例,下面我们会详细分析其中涉及到的概念。

Thunk

Thunk 可以让 action creator 返回一个函数代替一个 action 对象。该函数接收 dispatchgetState 方法作为参数,以便在需要的时候以后续的操作。这个额外的函数是在 action 创建函数内部自定义的,因此 action 创建函数自身不需要处理任何副作用。

示例

考虑如下的 action 创建函数:

-------- ---------------- -
  ------ -------- -- -
    ------------- -- -
      ----------------------
    -- ------
  --
-

这个函数不再返回一个 action 对象。相反,它在内部设置了 clearTimeout,其目的是延迟一秒钟,并在该时间之后调度一个函数。此函数再次调用 dispatch,此时在 1s 后将使用 increment() 来创建一个 action。

现在,与平常一样,用 connect() 函数把它 connect 到你的组件上。

------ - ------- - ---- --------------
------ - -------------- - ---- -------------------

----- ------- - -- ------ -------- -- -- -
  -----
    ----------------
    ------- ----------- -- ----------------------------
      --
    ---------
  ------
--

----- --------------- - ----- -- --
  ------ --------------
---

------ ------- --------
  ---------------
-----------

现在,我们可以谈论一下如何使用 thunk。

使用 Thunk

使 store 能够理解使用 thunk 的 action creator 需要用到 redux-thunk。可以通过引用此包后调用中间件来启用它。

------ - ------------ --------------- - ---- --------
------ ----- ---- --------------
------ ----------- ---- -------------

----- ----- - ------------
  ------------
  ----------------------
--

此时,无论从 action creator 返回什么类型的值,Redux 都能够接受。如果返回 action 对象,这只是正常工作机制。如果返回一个函数,则 Redux 会在合适时机执行该函数。

Saga

为了更好地管理异步操作,Redux Saga 提供了一种优雅的方式处理副作用。Saga 允许您编写像同步代码一样的异步操作。Redux Saga 具有一组测试确定的行为,使锁定它们变得极其容易。

Redux Saga 是以 Generator 函数作为基础构建的。Generator 函数被称为 Coroutine,它使用简单有助于理解的(短)Pseudoruntimes。“长”运行时间通常意味着,Saga 可以管理连续的异步操作,而无需使用回调或 Promises。另外,由于派生在此框架下的代码易于测试,因此开发者可以为 Saga 内的所有操作编写测试例子。

Saga 在许多方面都与 thunk 相似。它们都可以在 action 创建函数中进行异步操作。然而,在 thunk 中,必须掌握调度(通过 setTimeout 实现并控制回调函数中的代码)。相比之下,Saga 具有具有出色的可处理并发的能力。

Saga 特性

Saga 在设计之初考虑了将几个相关操作同时安排到进程中的复杂功能,是与其他异步中间件(如 thunk)相比,提供了一些显着的优势:

容易测试

与 thunk 中的副作用类似,Saga 中也存在操作副作用。但是,Saga 中的这些副作用都是作为迭代器函数中的简单 yield 语句定义的,并使用相同的技术轻松测试。这使得编写自动化单元测试非常容易。

可取消的操作

Saga 使您能够轻松定义可以并行执行、取消的操作,这是 thunk 所不能及的。这个功能几乎可以包括任何内容,从处理任何形式的异步操作,到有效处理用户交互。Saga 中的任何操作都可以被取消。

Synchronous-looking code

Saga action 是非阻塞协作任务(简单来说,是一种类型的并发任务)生成器。简而言之,它们包含像普通的 JavaScript 代码一样阻塞调用,但这些操作事实上是将异步操作分开执行的一种方法。Saga action 允许你不阻塞 Redux store 的渲染,同时完成长时间的异步操作。此外,Saga 功能使其非常容易管理同步联结操作。

如何使用 Saga

在启动 Saga 之前,库代码需要安装和引用。安装发生在 npm/yarn 中:

--- ------- ------ ----------

此时,您已经准备好向 store 应用中间件:

------ -------------------- ---- -------------
-- ------ ---- ----------
----- -------------- - -----------------------
----- ----- - ------------
  ------------
  -------------------------------
--
-- ---
-- -- ---- -----------
-----------------------------

在这个例子中,我们使用 createSagaMiddleware() 创建了 sagaMiddleware,然后使用 applyMiddleware() 将其添加到 store 中。

接下来,我们需要为 store 指定一个 rootSaga。这是我们的集合点,Saga 将从那里开始并发执行,并且可以与其他的 Saga 协同工作,以便针对用户的交互快速内部调用。

我们将来完善这个概念,现在看一个简单的 Saga。

Saga 示例

通过Saga来实现异步控制器,下面是一个切换语言的例子:

------ ------------ ---- ------ ---- ---------------------

------ --------- ---------------------- -
  ----- -----------
  ----- ----- ----- ------------------ -------- -------------- ---
-

------ --------- --------------------- -
  ----- ----------------------------- ----------------
-

在这个例子中,我们完全将一个 action 类型 (SWITCH_LANGUAGE) 的控制器交给了 Saga 来处理。这段代码使用了 takeLatest(),使得 Saga 在遇到最新的 action 时才实际发送请求。

上面的代码使用了以下几种效果。

  • put():发送一个 action
  • delay():等待指定毫秒后继续运行 Saga 控制器
  • take(value):当 store 中出现指定 value 时返回;不指定时,使用 takeLatest() 监听最新的 action。

上面的 Saga 可以理解为:

  • 等待来自 type 为 SWITCH_LANGUAGE 的 action
  • 在发送的 action 中包含 payload
  • 稍等 500ms
  • 放置一个 action(此示例中,名为 UPDATE_LANGUAGE)。

使用 Saga 和类 Promise 逻辑的区别在于 Saga

  • 适合处理有序异步操作
  • 具有多种各异的假设
  • 高度扩展性
  • 可测试。

Promise

在上面的两个例子中,都有异步处理。在异步的控制器中,我们需要确保我们设置了异步操作完成之后的 next 步骤。

Promise 对象刚刚好能够做到这点,在异步函数操作进行之后,通过一个箭头函数返回的 Promise 就可以调用下一个函数。这种模式是普遍效用的,因此在一些地方就会发现有实现自动方式的库存在。

例如,我们可以通过 axios 包获取一个 JSON 结果。如下:

------ ----- ---- --------

------ -------- --------------- -
  ------ -------------------------------------- -- ----------
-

如此一来,我们将 getImportData() 函数及其返回值纳入了一个 Promise 链,允许我们定义异步控制器及其后的操作。

Promise 实例

考虑国际式纯计算的例子。

------ - ------------ - ---- ----------------
----- ------------- - -------------------------------
----- ------------- - -------------------------------

------ ----- ------------ - -- --
  --- ----------------- ------- --
    ------------- -- ---------
      --------- ---------
      ------ ----
    --- -----
  --

------ --------- ---------- -
  --- -
    ----- ----- - ----- ---------------
    ----- --------------------------------
  - ----- ------- -
    ----- ---------------------
  -
-

在这个例子中,我们定义了一个 action,使用 createAction() 来生成它的常量。这个函数将一个字符串参数作为 action 类型。我们随后定义 getUserStats(),一个将返回一个 Promise 的函数。

接下来,我们在一个方案内检查用户的分数以了解其奖励水平:如果用户的分数(从 API 中获取)高于阈值,就向他们发放一个奖品,否则对他们进行些许惩罚。

在这个 Saga 中,我们首先将来自 getUserStats() 的数据解决在 stats 中。如果成功,我们使用 put() 发送了一个设置为该用户得分的 action。否则,我们调用 decreaseScore()

在两种情况下,handleError 和 isLoading 的状态都将被更新,同时我们的 Sagas 控制器退出。我们下面还将介绍一些有用的工具,可以帮助更好地管理 Saga。

结论

Redux 中间件是在应用程序的数据流中运行的组件,可以在动作被处理前和处理后抓取和操作它们。了解其中的核心概念能够帮助开发者更好地管理数据流和消除副作用。

Thunk、Saga 和 Promise 是如何轻松地处理异步操作的工具。它们之间有所不同,每种都适合不同种类的需要。Thunk 在简单应用程序中足够使用,而 Saga 适用于开发者要求更高的场景。Promise 可以为开发者提供快速的解决方案,减少了代码重复。在实际开发中,选择合适的工具是一个关键问题。

无论您选择哪种工具,写出复杂、高级别的代码时,每个都有所帮助。请记住,Redux middleware 是可测试的,并且易于管理。在使用时,保持小巧、有组织,并在必要的情况下添加注释,这些都是最佳实践。我希望这篇文章能够帮助您发掘和学习 Redux 中间件的潜力,开发出更优秀的应用。

来源:JavaScript中文网 ,转载请联系管理员! 本文地址:https://www.javascriptcn.com/post/66fca26d447136260170ef5c


猜你喜欢

  • ECMAScript 2017 中的字符串填充方法:String.padStart() 和 String.padEnd()

    在 JavaScript 中,字符串操作一直是前端开发中最基础也最常用的功能之一,ECMAScript 2017 标准中新增的字符串填充方法 String.padStart() 和 String.pa...

    18 天前
  • 以 Flex 布局构建响应式设计分割视图

    在当今网络应用程序生态系统中,设计响应式界面非常重要。这种技术允许用户适应不同设备和浏览器屏幕,并使应用程序对于各种设备尺寸都具有良好的适应性。因此,在开发前端应用程序时,设计响应式视图是必不可少的。

    18 天前
  • 如何在 Angular 应用中实现单元测试

    如何在 Angular 应用中实现单元测试 单元测试在软件工程中是非常重要的一部分,它可以提高代码质量和可维护性。对于 Angular 应用来说,单元测试同样也是不可或缺的。

    18 天前
  • 多方共建,让北京市无障碍发展健康前行

    多方共建,让北京市无障碍发展健康前行 随着互联网技术的迅猛发展,人们的交流和信息获取方式愈加多样化,但同时,我们也看到了无障碍互联网的重要性。 无障碍网站是指在设计、开发和使用时,考虑了所有人的需求,...

    18 天前
  • Sequelize(ORM)基础

    在开发现代 Web 应用时,数据存储是不可或缺的一部分。一般而言,应用需要连接数据库来存储和检索信息。但是,直接连接数据库并进行数据操作通常是困难的,因为大部分关系数据库(如 SQLite,Postg...

    18 天前
  • Deno 应用中如何处理 XML 格式数据

    引言 Deno 是一个新兴的 JavaScript 运行时环境,它与 Node.js 类似,但具有许多 Node.js 中缺失的特性,例如 TypeScript 的原生支持、安全的模块加载等等。

    18 天前
  • React 中的内联样式和外部样式表的区别

    React 是一种广泛使用的 JavaScript 库,用于开发用户界面。React 支持一种特殊的语法,称为 JSX,它使得将 HTML 和 JavaScript 混合使用变得更加简单和直观。

    18 天前
  • MongoDB 中如何使用 $elemMatch 进行子文档匹配

    简介 在 MongoDB 中,文档可以包含子文档,也就是嵌套文档。如果我们需要在查询中匹配一个文档的子文档,就需要使用 $elemMatch 操作符。$elemMatch 操作符用于在嵌套数组中进行元...

    18 天前
  • 响应式设计中低延时的图片加载技巧

    随着移动设备的普及,响应式设计已成为了现代网站开发的标配。在响应式设计中,图片的加载速度对用户体验至关重要。本文将介绍一些响应式图片加载的技巧,帮助您在低延时的情况下加载高质量的图片,提升用户体验。

    18 天前
  • ECMAScript 2019: 新的 Function 特性

    ECMAScript 2019: 新的 Function 特性 ECMAScript 2019(ES2019)是 JavaScript 的最新标准,并且添加了一些新的 Function 特性。

    18 天前
  • Kubernetes 使用 RBAC 进行权限管理实践

    前言 近年来,随着云原生技术的快速发展,Kubernetes 已成为云原生应用部署和管理的事实标准。而随着集群规模的扩大和业务复杂度的增加,如何对 Kubernetes 群集进行合理的权限管理变得尤为...

    18 天前
  • 解决在 Express.js 应用程序中使用 MongoDB 时的问题

    解决在 Express.js 应用程序中使用 MongoDB 时的问题 本文将讲解在 Express.js 应用程序中使用 MongoDB 时可能遇到的问题,并给出解决方案。

    18 天前
  • 如何在 Enzyme 中测试依赖 useContext 和 useReducer 实现的组件

    在 React 中使用 useContext 和 useReducer 处理状态管理逻辑已成为现代前端应用程序开发的一部分。然而,在测试这些组件时,可能会遇到一些挑战。

    18 天前
  • 关于 Vue SPA 应用 SEO 的一些实践案例

    背景介绍 Vue SPA(Single-Page Application)应用是指通过使用 Vue.js 框架创建的单页 web 应用程序。由于它们通过将内容加载到一个页面上来提供更流畅的用户体验,S...

    18 天前
  • Android 开发中 Material Design 的 CoordinatorLayout 实现方式

    在 Android 应用的开发中,Material Design 是不可缺少的一部分。Material Design 是一种设计和交互风格,它基于视觉层面的纸质布局与动态效果,而不是那些机械化而无情的...

    18 天前
  • 如何使用 PM2 检查 Node.js 应用程序的健康状态?

    Node.js 是一种广泛使用的 JavaScript 运行时,可用于构建高性能的网络应用程序和服务。在生产环境中运行 Node.js 应用程序时,我们需要确保它们始终处于健康状态。

    18 天前
  • ES7 实践:ESLint 常见的代码检查配置

    随着前端技术的不断进步,我们的代码变得越来越复杂,同时也越来越难以维护。为了避免代码质量问题,我们需要使用代码检查工具来确保我们的代码风格一致、符合规范,并且没有潜在的问题。

    18 天前
  • 使用 Socket.io 实现在线人数统计功能的方法

    前言 在互联网应用中,实时在线人数统计是一个非常常见的需求。今天我们来介绍如何使用 Socket.io 实现在线人数统计功能。 Socket.io 是一个实时通讯库,它基于 WebSockets、HT...

    18 天前
  • 如何使用 ES9 的 Proxy 实现数据双向绑定

    在前端开发中,数据双向绑定是一个很重要的概念。它可以使界面上的数据和数据模型保持同步,同时也可以提高开发效率和用户体验。在 ES9 中,引入了 Proxy 对象,可以方便地实现数据的双向绑定,本文将深...

    18 天前
  • TypeScript 中如何优化大型项目的开发和维护?

    前言:TypeScript 是一种 JavaScript 的超集,提供了类型检查和强类型支持,这使得它在大型项目中的开发和维护方面有着巨大的优势。在本文中,将介绍如何在 TypeScript 中使用一...

    18 天前

相关推荐

    暂无文章