搜索
首页web前端js教程深入研究Redux

A Deep Dive into Redux

核心要点

  • Redux 通过充当可预测的状态容器来简化现代应用程序中的状态管理,这对于维护应用程序在扩展时的稳定性至关重要。
  • TypeScript 集成通过强制类型安全来增强 Redux,这增加了一层可预测性,并通过简化重构来帮助维护大型代码库。
  • Redux 中的 reducer 被设计为纯函数,确保它不会产生副作用,从而增强了状态管理的可测试性和可靠性。
  • 使用 Jest 可以简化单元测试,Jest 与 TypeScript 无缝协作,用于测试 Redux 动作和 reducer,确保每个组件都能按预期工作。
  • 本文通过构建一个工资单引擎演示了 Redux 的实际实现,展示了 Redux 如何在实际应用程序场景中管理状态转换和处理副作用。

构建有状态的现代应用程序是一项复杂的任务。随着状态的改变,应用程序变得不可预测且难以维护。这就是 Redux 的用武之地。Redux 是一个轻量级的库,用于处理状态。可以把它想象成一个状态机。

在本文中,我将通过构建一个工资处理引擎来深入探讨 Redux 的状态容器。该应用程序将存储工资单以及所有额外内容,例如奖金和股票期权。我将使用纯 JavaScript 和 TypeScript 进行类型检查来保持解决方案的简洁性。由于 Redux 非常易于测试,我还将使用 Jest 来验证应用程序。

在本教程中,我假设您对 JavaScript、Node 和 npm 有一定的了解。

首先,您可以使用 npm 初始化此应用程序:

npm init

当询问测试命令时,请继续使用 jest。这意味着 npm t 将启动 Jest 并运行所有单元测试。主文件将是 index.js,以保持其简洁性。您可以随意回答 npm init 的其余问题。

我将使用 TypeScript 进行类型检查并确定数据模型。这有助于概念化我们正在尝试构建的内容。

要开始使用 TypeScript:

npm i typescript --save-dev

我将把开发工作流程中的一部分依赖项放在 devDependencies 中。这清楚地表明哪些依赖项是为开发人员准备的,哪些依赖项将用于生产环境。准备好 TypeScript 后,在 package.json 中添加一个启动脚本:

"start": "tsc && node .bin/index.js"

在 src 文件夹下创建一个 index.ts 文件。这将源文件与项目的其余部分分开。如果您执行 npm start,则解决方案将无法执行。这是因为您需要配置 TypeScript。

创建一个包含以下配置的 tsconfig.json 文件:

{
  "compilerOptions": {
    "strict": true,
    "lib": ["esnext", "dom"],
    "outDir": ".bin",
    "sourceMap": true
  },
  "files": [
    "src/index"
  ]
}

我本可以将此配置放在 tsc 命令行参数中。例如,tsc src/index.ts --strict .... 但是将所有这些放在单独的文件中要清晰得多。请注意,package.json 中的启动脚本只需要一个 tsc 命令。

以下是一些合理的编译器选项,它们将为我们提供一个良好的起点,以及每个选项的含义:

  • strict:启用所有严格类型检查选项,即 --noImplicitAny、--strictNullChecks 等。
  • lib:编译中包含的库文件列表。
  • outDir:将输出重定向到此目录。
  • sourceMap:生成用于调试的源映射文件。
  • files:提供给编译器的输入文件。

因为我将使用 Jest 进行单元测试,所以我将继续添加它:

npm init

ts-jest 依赖项为测试框架添加了类型检查。一个需要注意的地方是在 package.json 中添加一个 jest 配置:

npm i typescript --save-dev

这使得测试框架能够拾取 TypeScript 文件并知道如何对其进行转换。一个不错的功能是,您在运行单元测试时可以进行类型检查。为了确保此项目已准备好,请创建一个 __tests__ 文件夹,其中包含一个 index.test.ts 文件。然后,进行健全性检查。例如:

"start": "tsc && node .bin/index.js"

现在执行 npm start 和 npm t 将不会出现任何错误。这告诉我们我们现在可以开始构建解决方案了。但在我们这样做之前,让我们将 Redux 添加到项目中:

{
  "compilerOptions": {
    "strict": true,
    "lib": ["esnext", "dom"],
    "outDir": ".bin",
    "sourceMap": true
  },
  "files": [
    "src/index"
  ]
}

此依赖项将用于生产环境。因此,无需使用 --save-dev 包含它。如果您检查您的 package.json,它将位于 dependencies 中。

实际操作中的工资单引擎

工资单引擎将包含以下内容:工资、报销、奖金和股票期权。在 Redux 中,您不能直接更新状态。相反,会调度操作来通知存储任何新的更改。

因此,这留下了以下操作类型:

npm i jest ts-jest @types/jest @types/node --save-dev

PAY_DAY 操作类型可用于在发薪日发放支票并跟踪工资历史记录。这些操作类型在我们完善工资单引擎时指导其余的设计。它们捕获状态生命周期中的事件,例如设置基本工资金额。这些操作事件可以附加到任何内容,无论是点击事件还是数据更新。Redux 操作类型对于调度来自何处是抽象的。状态容器可以在客户端和/或服务器上运行。

TypeScript

使用类型理论,我将根据状态数据确定数据模型。对于每个工资单操作,例如操作类型和可选金额。金额是可选的,因为 PAY_DAY 不需要资金来处理工资单。我的意思是,它可以向客户收费,但现在先忽略它(也许在第二版中引入)。

例如,将其放在 src/index.ts 中:

"jest": {
  "preset": "ts-jest"
}

对于工资单状态,我们需要一个用于基本工资、奖金等的属性。我们还将使用此状态来维护工资历史记录。

此 TypeScript 接口应该可以做到:

npm init

对于每个属性,请注意 TypeScript 使用冒号指定类型。例如,: number。这确定了类型契约,并为类型检查器增加了可预测性。使用具有显式类型声明的类型系统可以增强 Redux。这是因为 Redux 状态容器是为可预测的行为而构建的。

这个想法并不疯狂或激进。《学习 Redux》第 1 章(仅限 SitePoint Premium 会员)对此进行了很好的解释。

随着应用程序的改变,类型检查增加了额外的可预测性。随着应用程序的扩展,类型理论也有助于简化大型代码段的重构。

现在使用类型概念化引擎有助于创建以下操作函数:

npm i typescript --save-dev

好的一点是,如果您尝试执行 processBasePay('abc'),类型检查器会向您发出警告。破坏类型契约会降低状态容器的可预测性。我使用像 PayrollAction 这样的单个操作契约来使工资处理器更可预测。请注意,金额通过 ES6 属性简写在操作对象中设置。更传统的方法是 amount: amount,这比较冗长。箭头函数,例如 () => ({}),是编写返回对象文字的函数的一种简洁方法。

reducer 作为纯函数

reducer 函数需要一个状态和一个操作参数。状态应该具有具有默认值的初始状态。那么,你能想象我们的初始状态可能是什么样子吗?我认为它需要从零开始,并带有一个空的工资历史记录列表。

例如:

"start": "tsc && node .bin/index.js"

类型检查器确保这些是属于此对象的正确值。有了初始状态,就开始创建 reducer 函数:

{
  "compilerOptions": {
    "strict": true,
    "lib": ["esnext", "dom"],
    "outDir": ".bin",
    "sourceMap": true
  },
  "files": [
    "src/index"
  ]
}

Redux reducer 具有一个模式,其中所有操作类型都由 switch 语句处理。但在遍历所有 switch case 之前,我将创建一个可重用的局部变量:

npm i jest ts-jest @types/jest @types/node --save-dev

请注意,如果您不改变全局状态,则可以改变局部变量。我使用 let 运算符来传达此变量将来会发生变化。改变全局状态(例如状态或操作参数)会导致 reducer 不纯。这种函数式范式至关重要,因为 reducer 函数必须保持纯净。《JavaScript 从新手到忍者》第 11 章(仅限 SitePoint Premium 会员)对此进行了解释。

开始 reducer 的 switch 语句以处理第一个用例:

"jest": {
  "preset": "ts-jest"
}

我使用 ES6 rest 运算符来保持状态属性不变。例如,...state。您可以在新对象中的 rest 运算符之后覆盖任何属性。basePay 来自解构,这很像其他语言中的模式匹配。computeTotalPay 函数设置如下:

it('is true', () => {
  expect(true).toBe(true);
});

请注意,您会扣除 stockOptions,因为这笔钱将用于购买公司股票。假设您想处理报销:

npm init

由于金额是可选的,请确保它具有默认值以减少故障。这就是 TypeScript 的优势所在,因为类型检查器会发现此陷阱并向您发出警告。类型系统知道某些事实,因此它可以做出合理的假设。假设您想处理奖金:

npm i typescript --save-dev

此模式使 reducer 可读,因为它只维护状态。您获取操作的金额,计算总工资,并创建一个新的对象文字。处理股票期权没有什么不同:

"start": "tsc && node .bin/index.js"

对于在发薪日处理工资单,它需要抹去奖金和报销。这两个属性不会在每个工资单中保留在状态中。并且,向工资历史记录中添加一个条目。基本工资和股票期权可以保留在状态中,因为它们不会经常更改。考虑到这一点,这就是 PAY_DAY 的处理方式:

{
  "compilerOptions": {
    "strict": true,
    "lib": ["esnext", "dom"],
    "outDir": ".bin",
    "sourceMap": true
  },
  "files": [
    "src/index"
  ]
}

在一个像 newPayHistory 这样的数组中,使用扩展运算符,它是 rest 的反义词。与收集对象中属性的 rest 不同,它会将项目展开。例如,[...payHistory]。尽管这两个运算符看起来很相似,但它们并不相同。仔细观察,因为这可能会出现在面试问题中。

对 payHistory 使用 pop() 不会改变状态。为什么?因为 slice() 返回一个全新的数组。JavaScript 中的数组是通过引用复制的。将数组分配给新变量不会更改底层对象。因此,在处理这些类型的对象时必须小心。

因为 lastPayHistory 有可能未定义,所以我使用穷人的空值合并来将其初始化为零。请注意 (o && o.property) || 0 模式用于合并。JavaScript 或甚至 TypeScript 的未来版本可能会有一种更优雅的方法来做到这一点。

每个 Redux reducer 都必须定义一个默认分支。为了确保状态不会变得未定义:

npm i jest ts-jest @types/jest @types/node --save-dev

测试 reducer 函数

编写纯函数的众多好处之一是它们易于测试。单元测试是指您必须期望可预测的行为的测试,您可以将所有测试作为构建的一部分自动化。在 __tests__/index.test.ts 中,取消虚拟测试并导入所有感兴趣的函数:

"jest": {
  "preset": "ts-jest"
}

请注意,所有函数都设置为导出,因此您可以导入它们。对于基本工资,启动工资单引擎 reducer 并对其进行测试:

it('is true', () => {
  expect(true).toBe(true);
});

Redux 将初始状态设置为未定义。因此,在 reducer 函数中提供默认值始终是一个好主意。处理报销怎么样?

npm i redux --save

处理奖金的模式与此相同:

const BASE_PAY = 'BASE_PAY';
const REIMBURSEMENT = 'REIMBURSEMENT';
const BONUS = 'BONUS';
const STOCK_OPTIONS = 'STOCK_OPTIONS';
const PAY_DAY = 'PAY_DAY';

对于股票期权:

interface PayrollAction {
  type: string;
  amount?: number;
}

请注意,当 stockOptions 大于 totalPay 时,totalPay 必须保持不变。由于这家假设的公司是合乎道德的,它不想从员工那里拿钱。如果您运行此测试,请注意 totalPay 设置为 -10,因为 stockOptions 会被扣除。这就是我们测试代码的原因!让我们修复计算总工资的地方:

npm init

如果员工赚的钱不够买公司股票,请继续跳过扣除。另外,确保它将 stockOptions 重置为零:

npm i typescript --save-dev

该修复程序确定了 newStockOptions 中他们是否有足够的钱。有了这个,单元测试通过,代码健全且有意义。我们可以测试有足够的钱进行扣除的积极用例:

"start": "tsc && node .bin/index.js"

对于发薪日,请使用多个状态进行测试,并确保一次性交易不会持续存在:

{
  "compilerOptions": {
    "strict": true,
    "lib": ["esnext", "dom"],
    "outDir": ".bin",
    "sourceMap": true
  },
  "files": [
    "src/index"
  ]
}

请注意,我如何调整 oldState 以验证奖金并将报销重置为零。

reducer 中的默认分支怎么样?

npm i jest ts-jest @types/jest @types/node --save-dev

Redux 在开始时设置了一个像 INIT_ACTION 这样的操作类型。我们只关心我们的 reducer 是否设置了一些初始状态。

整合所有内容

此时,您可能会开始怀疑 Redux 是否更像是一种设计模式。如果您回答它既是模式又是轻量级库,那么您是对的。在 index.ts 中,导入 Redux:

"jest": {
  "preset": "ts-jest"
}

下一个代码示例可以围绕此 if 语句包装。这是一个权宜之计,因此单元测试不会泄漏到集成测试中:

it('is true', () => {
  expect(true).toBe(true);
});

我不建议在实际项目中这样做。模块可以放在单独的文件中以隔离组件。这使其更易于阅读,并且不会泄漏问题。单元测试也受益于模块独立运行的事实。

使用 payrollEngineReducer 启动 Redux 存储:

npm i redux --save

每个 store.subscribe() 都返回一个后续的 unsubscribe() 函数,该函数可用于清理。它会在通过存储调度操作时取消订阅回调。在这里,我使用 store.getState() 将当前状态输出到控制台。

假设这位员工赚了 300,有 50 的报销,100 的奖金,以及 15 用于公司股票:

const BASE_PAY = 'BASE_PAY';
const REIMBURSEMENT = 'REIMBURSEMENT';
const BONUS = 'BONUS';
const STOCK_OPTIONS = 'STOCK_OPTIONS';
const PAY_DAY = 'PAY_DAY';

为了使其更有趣,再进行 50 的报销并处理另一张工资单:

interface PayrollAction {
  type: string;
  amount?: number;
}

最后,运行另一张工资单并取消订阅 Redux 存储:

interface PayStubState {
  basePay: number;
  reimbursement: number;
  bonus: number;
  stockOptions: number;
  totalPay: number;
  payHistory: Array<PayHistoryState>;
}

interface PayHistoryState {
  totalPay: number;
  totalCompensation: number;
}

最终结果如下所示:

export const processBasePay = (amount: number): PayrollAction =>
  ({type: BASE_PAY, amount});
export const processReimbursement = (amount: number): PayrollAction =>
  ({type: REIMBURSEMENT, amount});
export const processBonus = (amount: number): PayrollAction =>
  ({type: BONUS, amount});
export const processStockOptions = (amount: number): PayrollAction =>
  ({type: STOCK_OPTIONS, amount});
export const processPayDay = (): PayrollAction =>
  ({type: PAY_DAY});

如所示,Redux 维护状态、改变状态并在一个简洁的小包中通知订阅者。可以将 Redux 想象成一个状态机,它是状态数据的真实来源。所有这些都采用了编码的最佳实践,例如健全的函数式范式。

结论

Redux 为复杂的状态管理问题提供了一个简单的解决方案。它依赖于函数式范式来减少不可预测性。因为 reducer 是纯函数,所以单元测试非常容易。我决定使用 Jest,但是任何支持基本断言的测试框架都可以工作。

TypeScript 使用类型理论增加了额外的保护层。将类型检查与函数式编程结合起来,您将获得几乎不会中断的健全代码。最重要的是,TypeScript 在增加价值的同时不会妨碍工作。如果您注意到,一旦类型契约到位,几乎没有额外的编码。类型检查器会完成其余的工作。像任何好工具一样,TypeScript 在保持不可见的同时自动化编码纪律。TypeScript 吠叫声很大,但咬起来很轻。

如果您想试用此项目(我希望您这样做),您可以在 GitHub 上找到本文的源代码。

以上是深入研究Redux的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
node.js流带打字稿node.js流带打字稿Apr 30, 2025 am 08:22 AM

Node.js擅长于高效I/O,这在很大程度上要归功于流。 流媒体汇总处理数据,避免内存过载 - 大型文件,网络任务和实时应用程序的理想。将流与打字稿的类型安全结合起来创建POWE

Python vs. JavaScript:性能和效率注意事项Python vs. JavaScript:性能和效率注意事项Apr 30, 2025 am 12:08 AM

Python和JavaScript在性能和效率方面的差异主要体现在:1)Python作为解释型语言,运行速度较慢,但开发效率高,适合快速原型开发;2)JavaScript在浏览器中受限于单线程,但在Node.js中可利用多线程和异步I/O提升性能,两者在实际项目中各有优势。

JavaScript的起源:探索其实施语言JavaScript的起源:探索其实施语言Apr 29, 2025 am 12:51 AM

JavaScript起源于1995年,由布兰登·艾克创造,实现语言为C语言。1.C语言为JavaScript提供了高性能和系统级编程能力。2.JavaScript的内存管理和性能优化依赖于C语言。3.C语言的跨平台特性帮助JavaScript在不同操作系统上高效运行。

幕后:什么语言能力JavaScript?幕后:什么语言能力JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript在浏览器和Node.js环境中运行,依赖JavaScript引擎解析和执行代码。1)解析阶段生成抽象语法树(AST);2)编译阶段将AST转换为字节码或机器码;3)执行阶段执行编译后的代码。

Python和JavaScript的未来:趋势和预测Python和JavaScript的未来:趋势和预测Apr 27, 2025 am 12:21 AM

Python和JavaScript的未来趋势包括:1.Python将巩固在科学计算和AI领域的地位,2.JavaScript将推动Web技术发展,3.跨平台开发将成为热门,4.性能优化将是重点。两者都将继续在各自领域扩展应用场景,并在性能上有更多突破。

Python vs. JavaScript:开发环境和工具Python vs. JavaScript:开发环境和工具Apr 26, 2025 am 12:09 AM

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

JavaScript是用C编写的吗?检查证据JavaScript是用C编写的吗?检查证据Apr 25, 2025 am 12:15 AM

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

JavaScript的角色:使网络交互和动态JavaScript的角色:使网络交互和动态Apr 24, 2025 am 12:12 AM

JavaScript是现代网站的核心,因为它增强了网页的交互性和动态性。1)它允许在不刷新页面的情况下改变内容,2)通过DOMAPI操作网页,3)支持复杂的交互效果如动画和拖放,4)优化性能和最佳实践提高用户体验。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。