核心要點(diǎn)
- Redux 通過充當(dāng)可預(yù)測的狀態(tài)容器來簡化現(xiàn)代應(yīng)用程序中的狀態(tài)管理,這對於維護(hù)應(yīng)用程序在擴(kuò)展時的穩(wěn)定性至關(guān)重要。
- TypeScript 集成通過強(qiáng)制類型安全來增強(qiáng) Redux,這增加了一層可預(yù)測性,並通過簡化重構(gòu)來幫助維護(hù)大型代碼庫。
- Redux 中的 reducer 被設(shè)計為純函數(shù),確保它不會產(chǎn)生副作用,從而增強(qiáng)了狀態(tài)管理的可測試性和可靠性。
- 使用 Jest 可以簡化單元測試,Jest 與 TypeScript 無縫協(xié)作,用於測試 Redux 動作和 reducer,確保每個組件都能按預(yù)期工作。
- 本文通過構(gòu)建一個工資單引擎演示了 Redux 的實(shí)際實(shí)現(xiàn),展示了 Redux 如何在實(shí)際應(yīng)用程序場景中管理狀態(tài)轉(zhuǎn)換和處理副作用。
構(gòu)建有狀態(tài)的現(xiàn)代應(yīng)用程序是一項複雜的任務(wù)。隨著狀態(tài)的改變,應(yīng)用程序變得不可預(yù)測且難以維護(hù)。這就是 Redux 的用武之地。 Redux 是一個輕量級的庫,用於處理狀態(tài)??梢园阉胂癯梢粋€狀態(tài)機(jī)。
在本文中,我將通過構(gòu)建一個工資處理引擎來深入探討 Redux 的狀態(tài)容器。該應(yīng)用程序?qū)⒋鎯べY單以及所有額外內(nèi)容,例如獎金和股票期權(quán)。我將使用純 JavaScript 和 TypeScript 進(jìn)行類型檢查來保持解決方案的簡潔性。由於 Redux 非常易於測試,我還將使用 Jest 來驗(yàn)證應(yīng)用程序。
在本教程中,我假設(shè)您對 JavaScript、Node 和 npm 有一定的了解。
首先,您可以使用 npm 初始化此應(yīng)用程序:
npm init
當(dāng)詢問測試命令時,請繼續(xù)使用 jest。這意味著 npm t 將啟動 Jest 並運(yùn)行所有單元測試。主文件將是 index.js,以保持其簡潔性。您可以隨意回答 npm init 的其餘問題。
我將使用 TypeScript 進(jìn)行類型檢查並確定數(shù)據(jù)模型。這有助於概念化我們正在嘗試構(gòu)建的內(nèi)容。
要開始使用 TypeScript:
npm i typescript --save-dev
我將把開發(fā)工作流程中的一部分依賴項放在 devDependencies 中。這清楚地表明哪些依賴項是為開發(fā)人員準(zhǔn)備的,哪些依賴項將用於生產(chǎn)環(huán)境。準(zhǔn)備好 TypeScript 後,在 package.json 中添加一個啟動腳本:
"start": "tsc && node .bin/index.js"
在 src 文件夾下創(chuàng)建一個 index.ts 文件。這將源文件與項目的其餘部分分開。如果您執(zhí)行 npm start,則解決方案將無法執(zhí)行。這是因?yàn)槟枰渲?TypeScript。
創(chuàng)建一個包含以下配置的 tsconfig.json 文件:
{ "compilerOptions": { "strict": true, "lib": ["esnext", "dom"], "outDir": ".bin", "sourceMap": true }, "files": [ "src/index" ] }
我本可以將此配置放在 tsc 命令行參數(shù)中。例如,tsc src/index.ts --strict .... 但是將所有這些放在單獨(dú)的文件中要清晰得多。請注意,package.json 中的啟動腳本只需要一個 tsc 命令。
以下是一些合理的編譯器選項,它們將為我們提供一個良好的起點(diǎn),以及每個選項的含義:
- strict:啟用所有嚴(yán)格類型檢查選項,即 --noImplicitAny、--strictNullChecks 等。
- lib:編譯中包含的庫文件列表。
- outDir:將輸出重定向到此目錄。
- sourceMap:生成用於調(diào)試的源映射文件。
- files:提供給編譯器的輸入文件。
因?yàn)槲覍⑹褂?Jest 進(jìn)行單元測試,所以我將繼續(xù)添加它:
npm init
ts-jest 依賴項為測試框架添加了類型檢查。一個需要注意的地方是在 package.json 中添加一個 jest 配置:
npm i typescript --save-dev
這使得測試框架能夠拾取 TypeScript 文件並知道如何對其進(jìn)行轉(zhuǎn)換。一個不錯的功能是,您在運(yùn)行單元測試時可以進(jìn)行類型檢查。為了確保此項目已準(zhǔn)備好,請創(chuàng)建一個 __tests__ 文件夾,其中包含一個 index.test.ts 文件。然後,進(jìn)行健全性檢查。例如:
"start": "tsc && node .bin/index.js"
現(xiàn)在執(zhí)行 npm start 和 npm t 將不會出現(xiàn)任何錯誤。這告訴我們我們現(xiàn)在可以開始構(gòu)建解決方案了。但在我們這樣做之前,讓我們將 Redux 添加到項目中:
{ "compilerOptions": { "strict": true, "lib": ["esnext", "dom"], "outDir": ".bin", "sourceMap": true }, "files": [ "src/index" ] }
此依賴項將用於生產(chǎn)環(huán)境。因此,無需使用 --save-dev 包含它。如果您檢查您的 package.json,它將位於 dependencies 中。
實(shí)際操作中的工資單引擎
工資單引擎將包含以下內(nèi)容:工資、報銷、獎金和股票期權(quán)。在 Redux 中,您不能直接更新狀態(tài)。相反,會調(diào)度操作來通知存儲任何新的更改。
因此,這留下了以下操作類型:
npm i jest ts-jest @types/jest @types/node --save-dev
PAY_DAY 操作類型可用於在發(fā)薪日發(fā)放支票並跟蹤工資歷史記錄。這些操作類型在我們完善工資單引擎時指導(dǎo)其餘的設(shè)計。它們捕獲狀態(tài)生命週期中的事件,例如設(shè)置基本工資金額。這些操作事件可以附加到任何內(nèi)容,無論是點(diǎn)擊事件還是數(shù)據(jù)更新。 Redux 操作類型對於調(diào)度來自何處是抽象的。狀態(tài)容器可以在客戶端和/或服務(wù)器上運(yùn)行。
TypeScript
使用類型理論,我將根據(jù)狀態(tài)數(shù)據(jù)確定數(shù)據(jù)模型。對於每個工資單操作,例如操作類型和可選金額。金額是可選的,因?yàn)?PAY_DAY 不需要資金來處理工資單。我的意思是,它可以向客戶收費(fèi),但現(xiàn)在先忽略它(也許在第二版中引入)。
例如,將其放在 src/index.ts 中:
"jest": { "preset": "ts-jest" }
對於工資單狀態(tài),我們需要一個用於基本工資、獎金等的屬性。我們還將使用此狀態(tài)來維護(hù)工資歷史記錄。
此 TypeScript 接口應(yīng)該可以做到:
npm init
對於每個屬性,請注意 TypeScript 使用冒號指定類型。例如,: number。這確定了類型契約,並為類型檢查器增加了可預(yù)測性。使用具有顯式類型聲明的類型系統(tǒng)可以增強(qiáng) Redux。這是因?yàn)?Redux 狀態(tài)容器是為可預(yù)測的行為而構(gòu)建的。
這個想法並不瘋狂或激進(jìn)。 《學(xué)習(xí) Redux》第 1 章(僅限 SitePoint Premium 會員)對此進(jìn)行了很好的解釋。
隨著應(yīng)用程序的改變,類型檢查增加了額外的可預(yù)測性。隨著應(yīng)用程序的擴(kuò)展,類型理論也有助於簡化大型代碼段的重構(gòu)。
現(xiàn)在使用類型概念化引擎有助於創(chuàng)建以下操作函數(shù):
npm i typescript --save-dev
好的一點(diǎn)是,如果您嘗試執(zhí)行 processBasePay('abc'),類型檢查器會向您發(fā)出警告。破壞類型契約會降低狀態(tài)容器的可預(yù)測性。我使用像 PayrollAction 這樣的單個操作契約來使工資處理器更可預(yù)測。請注意,金額通過 ES6 屬性簡寫在操作對像中設(shè)置。更傳統(tǒng)的方法是 amount: amount,這比較冗長。箭頭函數(shù),例如 () => ({}),是編寫返回對象文字的函數(shù)的一種簡潔方法。
reducer 作為純函數(shù)
reducer 函數(shù)需要一個狀態(tài)和一個操作參數(shù)。狀態(tài)應(yīng)該具有具有默認(rèn)值的初始狀態(tài)。那麼,你能想像我們的初始狀態(tài)可能是什麼樣子嗎?我認(rèn)為它需要從零開始,並帶有一個空的工資歷史記錄列表。
例如:
"start": "tsc && node .bin/index.js"
類型檢查器確保這些是屬於此對象的正確值。有了初始狀態(tài),就開始創(chuàng)建 reducer 函數(shù):
{ "compilerOptions": { "strict": true, "lib": ["esnext", "dom"], "outDir": ".bin", "sourceMap": true }, "files": [ "src/index" ] }
Redux reducer 具有一個模式,其中所有操作類型都由 switch 語句處理。但在遍歷所有 switch case 之前,我將創(chuàng)建一個可重用的局部變量:
npm i jest ts-jest @types/jest @types/node --save-dev
請注意,如果您不改變?nèi)譅顟B(tài),則可以改變局部變量。我使用 let 運(yùn)算符來傳達(dá)此變量將來會發(fā)生變化。改變?nèi)譅顟B(tài)(例如狀態(tài)或操作參數(shù))會導(dǎo)致 reducer 不純。這種函數(shù)式範(fàn)式至關(guān)重要,因?yàn)?reducer 函數(shù)必須保持純淨(jìng)。 《JavaScript 從新手到忍者》第 11 章(僅限 SitePoint Premium 會員)對此進(jìn)行了解釋。
開始 reducer 的 switch 語句以處理第一個用例:
"jest": { "preset": "ts-jest" }
我使用 ES6 rest 運(yùn)算符來保持狀態(tài)屬性不變。例如,...state。您可以在新對像中的 rest 運(yùn)算符之後覆蓋任何屬性。 basePay 來自解構(gòu),這很像其他語言中的模式匹配。 computeTotalPay 函數(shù)設(shè)置如下:
it('is true', () => { expect(true).toBe(true); });
請注意,您會扣除 stockOptions,因?yàn)檫@筆錢將用於購買公司股票。假設(shè)您想處理報銷:
npm init
由於金額是可選的,請確保它具有默認(rèn)值以減少故障。這就是 TypeScript 的優(yōu)勢所在,因?yàn)轭愋蜋z查器會發(fā)現(xiàn)此陷阱並向您發(fā)出警告。類型系統(tǒng)知道某些事實(shí),因此它可以做出合理的假設(shè)。假設(shè)您想處理獎金:
npm i typescript --save-dev
此模式使 reducer 可讀,因?yàn)樗痪S護(hù)狀態(tài)。您獲取操作的金額,計算總工資,並創(chuàng)建一個新的對象文字。處理股票期權(quán)沒有什麼不同:
"start": "tsc && node .bin/index.js"
對於在發(fā)薪日處理工資單,它需要抹去獎金和報銷。這兩個屬性不會在每個工資單中保留在狀態(tài)中。並且,向工資歷史記錄中添加一個條目?;竟べY和股票期權(quán)可以保留在狀態(tài)中,因?yàn)樗鼈儾粫?jīng)常更改??紤]到這一點(diǎn),這就是 PAY_DAY 的處理方式:
{ "compilerOptions": { "strict": true, "lib": ["esnext", "dom"], "outDir": ".bin", "sourceMap": true }, "files": [ "src/index" ] }
在一個像 newPayHistory 這樣的數(shù)組中,使用擴(kuò)展運(yùn)算符,它是 rest 的反義詞。與收集對像中屬性的 rest 不同,它會將項目展開。例如,[...payHistory]。儘管這兩個運(yùn)算符看起來很相似,但它們並不相同。仔細(xì)觀察,因?yàn)檫@可能會出現(xiàn)在面試問題中。
對 payHistory 使用 pop() 不會改變狀態(tài)。為什麼?因?yàn)?slice() 返回一個全新的數(shù)組。 JavaScript 中的數(shù)組是通過引用複制的。將數(shù)組分配給新變量不會更改底層對象。因此,在處理這些類型的對象時必須小心。
因?yàn)?lastPayHistory 有可能未定義,所以我使用窮人的空值合併來將其初始化為零。請注意 (o && o.property) || 0 模式用於合併。 JavaScript 或甚至 TypeScript 的未來版本可能會有一種更優(yōu)雅的方法來做到這一點(diǎn)。
每個 Redux reducer 都必須定義一個默認(rèn)分支。為了確保狀態(tài)不會變得未定義:
npm i jest ts-jest @types/jest @types/node --save-dev
測試 reducer 函數(shù)
編寫純函數(shù)的眾多好處之一是它們易於測試。單元測試是指您必須期望可預(yù)測的行為的測試,您可以將所有測試作為構(gòu)建的一部分自動化。在 __tests__/index.test.ts 中,取消虛擬測試並導(dǎo)入所有感興趣的函數(shù):
"jest": { "preset": "ts-jest" }
請注意,所有函數(shù)都設(shè)置為導(dǎo)出,因此您可以導(dǎo)入它們。對於基本工資,啟動工資單引擎 reducer 並對其進(jìn)行測試:
it('is true', () => { expect(true).toBe(true); });
Redux 將初始狀態(tài)設(shè)置為未定義。因此,在 reducer 函數(shù)中提供默認(rèn)值始終是一個好主意。處理報銷怎麼樣?
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';
對於股票期權(quán):
interface PayrollAction { type: string; amount?: number; }
請注意,當(dāng) stockOptions 大於 totalPay 時,totalPay 必須保持不變。由於這家假設(shè)的公司是合乎道德的,它不想從員工那裡拿錢。如果您運(yùn)行此測試,請注意 totalPay 設(shè)置為 -10,因?yàn)?stockOptions 會被扣除。這就是我們測試代碼的原因!讓我們修復(fù)計算總工資的地方:
npm init
如果員工賺的錢不夠買公司股票,請繼續(xù)跳過扣除。另外,確保它將 stockOptions 重置為零:
npm i typescript --save-dev
該修復(fù)程序確定了 newStockOptions 中他們是否有足夠的錢。有了這個,單元測試通過,代碼健全且有意義。我們可以測試有足夠的錢進(jìn)行扣除的積極用例:
"start": "tsc && node .bin/index.js"
對於發(fā)薪日,請使用多個狀態(tài)進(jìn)行測試,並確保一次性交易不會持續(xù)存在:
{ "compilerOptions": { "strict": true, "lib": ["esnext", "dom"], "outDir": ".bin", "sourceMap": true }, "files": [ "src/index" ] }
請注意,我如何調(diào)整 oldState 以驗(yàn)證獎金並將報銷重置為零。
reducer 中的默認(rèn)分支怎麼樣?
npm i jest ts-jest @types/jest @types/node --save-dev
Redux 在開始時設(shè)置了一個像 INIT_ACTION 這樣的操作類型。我們只關(guān)心我們的 reducer 是否設(shè)置了一些初始狀態(tài)。
整合所有內(nèi)容
此時,您可能會開始懷疑 Redux 是否更像是一種設(shè)計模式。如果您回答它既是模式又是輕量級庫,那麼您是對的。在 index.ts 中,導(dǎo)入 Redux:
"jest": { "preset": "ts-jest" }
下一個代碼示例可以圍繞此 if 語句包裝。這是一個權(quán)宜之計,因此單元測試不會洩漏到集成測試中:
it('is true', () => { expect(true).toBe(true); });
我不建議在實(shí)際項目中這樣做。模塊可以放在單獨(dú)的文件中以隔離組件。這使其更易於閱讀,並且不會洩漏問題。單元測試也受益於模塊獨(dú)立運(yùn)行的事實(shí)。
使用 payrollEngineReducer 啟動 Redux 存儲:
npm i redux --save
每個 store.subscribe() 都返回一個後續(xù)的 unsubscribe() 函數(shù),該函數(shù)可用於清理。它會在通過存儲調(diào)度操作時取消訂閱回調(diào)。在這裡,我使用 store.getState() 將當(dāng)前狀態(tài)輸出到控制臺。
假設(shè)這位員工賺了 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';
為了使其更有趣,再進(jìn)行 50 的報銷並處理另一張工資單:
interface PayrollAction { type: string; amount?: number; }
最後,運(yùn)行另一張工資單並取消訂閱 Redux 存儲:
interface PayStubState { basePay: number; reimbursement: number; bonus: number; stockOptions: number; totalPay: number; payHistory: Array<PayHistoryState>; } interface PayHistoryState { totalPay: number; totalCompensation: number; }
最終結(jié)果如下所示:
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 維護(hù)狀態(tài)、改變狀態(tài)並在一個簡潔的小包中通知訂閱者??梢詫?Redux 想像成一個狀態(tài)機(jī),它是狀態(tài)數(shù)據(jù)的真實(shí)來源。所有這些都採用了編碼的最佳實(shí)踐,例如健全的函數(shù)式範(fàn)式。
結(jié)論
Redux 為複雜的狀態(tài)管理問題提供了一個簡單的解決方案。它依賴於函數(shù)式範(fàn)式來減少不可預(yù)測性。因?yàn)?reducer 是純函數(shù),所以單元測試非常容易。我決定使用 Jest,但是任何支持基本斷言的測試框架都可以工作。
TypeScript 使用類型理論增加了額外的保護(hù)層。將類型檢查與函數(shù)式編程結(jié)合起來,您將獲得幾乎不會中斷的健全代碼。最重要的是,TypeScript 在增加價值的同時不會妨礙工作。如果您注意到,一旦類型契約到位,幾乎沒有額外的編碼。類型檢查器會完成其餘的工作。像任何好工具一樣,TypeScript 在保持不可見的同時自動化編碼紀(jì)律。 TypeScript 吠叫聲很大,但咬起來很輕。
如果您想試用此項目(我希望您這樣做),您可以在 GitHub 上找到本文的源代碼。
以上是深入研究Redux的詳細(xì)內(nèi)容。更多資訊請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費(fèi)脫衣圖片

Undresser.AI Undress
人工智慧驅(qū)動的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強(qiáng)大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6
視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

javascriptisidealforwebdevelogment,whilejavasuitslarge-scaleapplicationsandandandroiddevelopment.1)javascriptexceleatingingingingingingingbeatingwebexperienceswebexperienceswebexperiencesandfull-stackdeevermentwithnode.js.2)

在JavaScript中,選擇單行註釋(//)還是多行註釋(//)取決於註釋的目的和項目需求:1.使用單行註釋進(jìn)行快速、內(nèi)聯(lián)的解釋;2.使用多行註釋進(jìn)行詳細(xì)的文檔說明;3.保持註釋風(fēng)格的一致性;4.避免過度註釋;5.確保註釋與代碼同步更新。選擇合適的註釋風(fēng)格有助於提高代碼的可讀性和可維護(hù)性。

是的,javascriptcommentsarenectary和shouldshouldshouldseffectional.1)他們通過codeLogicAndIntentsgudedepleders,2)asevitalincomplexprojects,和3)handhanceClaritywithOutClutteringClutteringThecode。

Java和JavaScript是不同的編程語言,各自適用於不同的應(yīng)用場景。 Java用於大型企業(yè)和移動應(yīng)用開發(fā),而JavaScript主要用於網(wǎng)頁開發(fā)。

JavascriptconcommentsenceenceEncorenceEnterential gransimenting,reading and guidingCodeeXecution.1)單inecommentsareusedforquickexplanations.2)多l(xiāng)inecommentsexplaincomplexlogicorprovideDocumentation.3)

評論arecrucialinjavascriptformaintainingclarityclarityandfosteringCollaboration.1)heelpindebugging,登機(jī),andOnderStandingCodeeVolution.2)使用林格forquickexexplanations andmentmentsmmentsmmentsmments andmmentsfordeffordEffordEffordEffordEffordEffordEffordEffordEddeScriptions.3)bestcractices.3)bestcracticesincracticesinclud

JavaScripthasseveralprimitivedatatypes:Number,String,Boolean,Undefined,Null,Symbol,andBigInt,andnon-primitivetypeslikeObjectandArray.Understandingtheseiscrucialforwritingefficient,bug-freecode:1)Numberusesa64-bitformat,leadingtofloating-pointissuesli

JavaScriptIspreferredforredforwebdevelverment,而Javaisbetterforlarge-ScalebackendsystystemsandSandAndRoidApps.1)JavascriptexcelcelsincreatingInteractiveWebexperienceswebexperienceswithitswithitsdynamicnnamicnnamicnnamicnnamicnemicnemicnemicnemicnemicnemicnemicnemicnddommanipulation.2)
