搜尋
首頁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
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)優化性能和最佳實踐提高用戶體驗。

C和JavaScript:連接解釋C和JavaScript:連接解釋Apr 23, 2025 am 12:07 AM

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

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

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具