首頁 > web前端 > js教程 > 沒有反應的redux

沒有反應的redux

尊渡假赌尊渡假赌尊渡假赌
發布: 2025-02-17 11:09:13
原創
642 人瀏覽過

Redux without React

本文經Vildan Softic同行評審。感謝所有SitePoint的同行評審員,讓SitePoint的內容盡善盡美!

Redux without React 我屬於那種喜歡從零開始,並了解一切工作原理的開發者。雖然我知道這給自己帶來了(不必要的)工作量,但這確實幫助我欣賞和理解特定框架、庫或模塊背後的機制。

最近,我又經歷了這樣的時刻,開始使用Redux和純JavaScript開發一個Web應用程序。在本文中,我想概述我的應用程序結構,檢查我早期(最終失敗的)迭代,然後看看我最終選擇的解決方案以及在此過程中學到的知識。

關鍵要點

  • Redux可以不依賴React,使用純JavaScript管理應用程序狀態,展示了其在不同UI層中進行狀態管理的靈活性。
  • 正確初始化和管理Redux存儲至關重要;應在應用程序入口點創建存儲,並將其傳遞給組件,以避免循環依賴等問題。
  • 在僅使用Redux的設置中,組件可以類似於React的組件結構,分為表現層和容器層,這有助於分離關注點並明確角色定義。
  • 與React的虛擬DOM(根據狀態更新自動處理UI更改)不同,在使用純JavaScript和Redux時,需要手動更新DOM。
  • 建議實現用例驅動的存儲,確保僅存儲必要的數據,這可以通過避免不必要的狀態持久化來提高性能和用戶體驗。

設置

您可能聽說過流行的React.js和Redux組合,它使用最新的前端技術構建快速而強大的Web應用程序。

React是由Facebook創建的用於構建用戶界面的基於組件的開源庫。雖然React只是一個視圖層(不是像Angular或Ember這樣的完整框架),但Redux管理應用程序的狀態。它充當可預測的狀態容器,其中整個狀態存儲在一個單一的對象樹中,並且只能通過發出所謂的action來更改。如果您完全不了解這個主題,我建議您閱讀這篇文章。

對於本文的其餘部分,不需要成為Redux專家,但至少對它的概念有一定的了解會有所幫助。

無React的Redux——從零開始的應用程序

Redux的優點在於它迫使您提前思考並儘早了解應用程序的設計。您開始定義實際應該存儲什麼,哪些數據可以並且應該更改,以及哪些組件可以訪問存儲。但由於Redux只關注狀態,我發現自己有點困惑如何構建和連接應用程序的其餘部分。 React在引導您完成所有步驟方面做得很好,但如果沒有它,就需要我弄清楚什麼方法最有效。

所討論的應用程序是一個針對移動設備優先的俄羅斯方塊克隆,它有幾個不同的視圖。實際的遊戲邏輯在Redux中完成,而離線功能由localStorage和自定義視圖處理提供。存儲庫可以在GitHub上找到,儘管該應用程序仍在積極開發中,並且我是在開發過程中撰寫這篇文章的。

定義應用程序架構

我決定採用在Redux和React項目中常見的文件夾結構。這是一個邏輯結構,適用於許多不同的設置。這個主題有很多變體,大多數項目都略有不同,但整體結構相同。

src/scripts/

<code>actions/
├── game.js
├── score.js
└── ...
components/
├── router.js
├── pageControls.js
├── canvas.js
└── ...
constants/
├── game.js
├── score.js
└── ...
reducers/
├── game.js
├── score.js
└── ...
store/
├── configureStore.js
├── connect.js
└── index.js
utils/
├── serviceWorker.js
├── localStorage.js
├── dom.js
└── ...
index.js
worker.js</code>
登入後複製
登入後複製
登入後複製

我的標記被分隔到另一個目錄中,最終由單個index.html文件呈現。該結構類似於scripts/,以便在整個代碼庫中保持一致的架構。

src/markup/

<code>layouts/
└── default.html
partials/
├── back-button.html
└── meta.html
pages/
├── about.html
├── settings.html
└── ...
index.html</code>
登入後複製
登入後複製
登入後複製

管理和訪問存儲

要訪問存儲,需要創建一次並將其傳遞給應用程序的所有實例。大多數框架都使用某種依賴注入容器,因此我們作為框架的用戶不必自己想出解決方案。但是,當我使用自己的解決方案時,如何才能讓它對我的所有組件都可用呢?

我的第一次迭代失敗了。我不知道為什麼我認為這是一個好主意,但我將存儲放在它自己的模塊(scripts/store/index.js)中,然後可以由應用程序的其他部分導入。我最終後悔了,並很快處理了循環依賴。問題是,當組件嘗試訪問存儲時,存儲沒有正確初始化。我製作了一個圖表來演示我正在處理的依賴關係流程:

Redux without React

應用程序入口點正在初始化所有組件,然後通過直接或通過輔助函數(此處稱為connect)內部使用存儲。但是,由於存儲不是顯式創建的,而只是在其自身模塊中的副作用,組件最終在存儲創建之前就使用了存儲。無法控制組件或輔助函數第一次調用存儲的時間。這很混亂。

存儲模塊如下所示:

scripts/store/index.js (☓ bad)

import { createStore } from 'redux'
import reducers from '../reducers'

const store = createStore(reducers)

export default store
export { getItemList } from './connect'
登入後複製
登入後複製
登入後複製

如上所述,存儲是作為副作用創建的,然後導出。輔助函數也需要存儲。

scripts/store/connect.js (☓ bad)

import store from './'

export function getItemList () {
  return store.getState().items.all
}
登入後複製
登入後複製
登入後複製

這正是我的組件最終相互遞歸的時刻。輔助函數需要存儲才能運行,並且同時從存儲初始化文件中導出,以便使它們可以訪問應用程序的其他部分。您看到這聽起來有多亂嗎?

解決方案

現在看來很明顯的事情,我花了一段時間才理解。我通過將初始化移動到我的應用程序入口點(scripts/index.js),並將其傳遞給所有必需的組件來解決此問題。

同樣,這與React實際使存儲可訪問的方式非常相似(查看源代碼)。它們一起工作得如此之好是有原因的,為什麼不學習它的概念呢?

Redux without React

應用程序入口點首先創建存儲,然後將其傳遞給所有組件。然後,組件可以連接到存儲並調度操作、訂閱更改或獲取特定數據。

讓我們來看一下更改:

scripts/store/configureStore.js (✓ good)

<code>actions/
├── game.js
├── score.js
└── ...
components/
├── router.js
├── pageControls.js
├── canvas.js
└── ...
constants/
├── game.js
├── score.js
└── ...
reducers/
├── game.js
├── score.js
└── ...
store/
├── configureStore.js
├── connect.js
└── index.js
utils/
├── serviceWorker.js
├── localStorage.js
├── dom.js
└── ...
index.js
worker.js</code>
登入後複製
登入後複製
登入後複製

我保留了該模塊,但改為了導出一個名為configureStore的函數,該函數在代碼庫中的其他地方創建存儲。 請注意,這只是基本概念;我還使用了Redux DevTools擴展程序並通過localStorage加載持久化狀態。

scripts/store/connect.js (✓ good)

<code>layouts/
└── default.html
partials/
├── back-button.html
└── meta.html
pages/
├── about.html
├── settings.html
└── ...
index.html</code>
登入後複製
登入後複製
登入後複製

connect輔助函數基本上沒有改變,但現在需要將存儲作為參數傳遞。起初我猶豫是否要使用此解決方案,因為我認為“那麼輔助函數有什麼意義呢?”。現在我認為它們很好而且足夠高級,使一切更易於閱讀。

scripts/index.js

import { createStore } from 'redux'
import reducers from '../reducers'

const store = createStore(reducers)

export default store
export { getItemList } from './connect'
登入後複製
登入後複製
登入後複製

這是應用程序入口點。存儲被創建,並傳遞給所有組件。 PageControls為特定操作按鈕添加全局事件偵聽器,TetrisGame是實際的遊戲組件。在將存儲移到這里之前,它看起來基本相同,但沒有將存儲分別傳遞給所有模塊。如前所述,組件可以通過我失敗的連接方法訪問存儲。

組件

我決定使用兩種組件:表現層容器組件。表現層組件除了純DOM處理之外什麼也不做;它們不知道存儲。另一方面,容器組件可以調度操作或訂閱更改。

Dan Abramov已經為React組件寫了一篇很棒的文章,但這套方法也可以應用於任何其他組件架構。

不過,對我來說也有例外。有時組件非常小,只做一件事情。我不想將它們分成上述模式之一,所以我決定將它們混合使用。如果組件增長並獲得更多邏輯,我將對其進行分離。

scripts/components/pageControls.js

import store from './'

export function getItemList () {
  return store.getState().items.all
}
登入後複製
登入後複製
登入後複製

上面的示例就是其中一個組件。它有一個元素列表(在本例中是所有具有data-action屬性的元素),並根據屬性內容在單擊時調度操作。僅此而已。然後,其他模塊可能會偵聽存儲中的更改並相應地更新自身。如前所述,如果組件還進行了DOM更新,我將對其進行分離。

現在,讓我向您展示這兩種組件類型的清晰分離。

更新DOM

在我開始該項目時,我遇到的一個更大的問題是如何實際更新DOM。 React使用稱為虛擬DOM的DOM的快速內存中表示來最大限度地減少DOM更新。

我實際上是在考慮做同樣的事情,如果我的應用程序變得更大且DOM更繁重,我可能會切換到虛擬DOM,但就目前而言,我進行經典DOM操作,這與Redux配合得很好。

基本流程如下:

  • 初始化容器組件的新實例並傳遞存儲以供內部使用
  • 組件訂閱存儲中的更改
  • 並使用不同的表現層組件在DOM中呈現更新

注意:對於JavaScript中與DOM相關的任何內容,我都是$符號前綴的粉絲。正如您可能猜到的那樣,它取自jQuery的$。因此,純表現層組件文件名以美元符號為前綴。

scripts/index.js

<code>actions/
├── game.js
├── score.js
└── ...
components/
├── router.js
├── pageControls.js
├── canvas.js
└── ...
constants/
├── game.js
├── score.js
└── ...
reducers/
├── game.js
├── score.js
└── ...
store/
├── configureStore.js
├── connect.js
└── index.js
utils/
├── serviceWorker.js
├── localStorage.js
├── dom.js
└── ...
index.js
worker.js</code>
登入後複製
登入後複製
登入後複製

這裡沒有什麼花哨的東西。導入、創建和初始化容器組件ScoreObserver。它究竟做了什麼?它更新所有與分數相關的視圖元素:高分列表和遊戲期間的當前分數信息。

scripts/components/scoreObserver/index.js

<code>layouts/
└── default.html
partials/
├── back-button.html
└── meta.html
pages/
├── about.html
├── settings.html
└── ...
index.html</code>
登入後複製
登入後複製
登入後複製

請記住,這是一個簡單的組件;其他組件可能具有更複雜的邏輯和需要處理的事情。這裡發生了什麼? ScoreObserver組件保存對存儲的內部引用,並創建新實例的兩個表現層組件以供以後使用。 init方法訂閱存儲更新,並在每次存儲更改時更新$label組件——但前提是遊戲實際上正在運行。

updateScoreBoard方法在其他地方使用。每次發生更改時更新列表是沒有意義的,因為視圖無論如何都是不活動的。還有一個路由組件,它在每次視圖更改時更新或停用不同的組件。它的API大致如下所示:

import { createStore } from 'redux'
import reducers from '../reducers'

const store = createStore(reducers)

export default store
export { getItemList } from './connect'
登入後複製
登入後複製
登入後複製

注意:$(和$$)不是jQuery引用,而是document.querySelector的便捷實用程序快捷方式。

scripts/components/scoreObserver/$board.js

import store from './'

export function getItemList () {
  return store.getState().items.all
}
登入後複製
登入後複製
登入後複製

同樣,這是一個基本示例和一個基本組件。 updateBoard()方法獲取一個數組,對其進行迭代,並將內容插入到分數列表中。

scripts/components/scoreObserver/$label.js

import { createStore } from 'redux'
import reducers from '../reducers'

export default function configureStore () {
  return createStore(reducers)
}
登入後複製

此組件與上面的ScoreBoard幾乎完全相同,但只更新單個元素。

其他錯誤和建議

另一個重要點是實現用例驅動的存儲。我認為只存儲對應用程序必不可少的內容很重要。一開始,我幾乎存儲了一切:當前活動視圖、遊戲設置、分數、懸停效果、用戶的呼吸模式等等。

雖然這可能與一個應用程序相關,但與另一個應用程序無關。存儲當前視圖並在重新加載時繼續在完全相同的位置可能很好,但在我的情況下,這感覺像是糟糕的用戶體驗,而且比有用的更煩人。您也不想存儲菜單或模態的切換,對吧?用戶為什麼要回到那個特定狀態?在較大的Web應用程序中,這可能是有意義的。但在我的小型移動遊戲重點遊戲中,回到設置屏幕只是因為我從那裡離開,這相當煩人。

結論

我已經使用和不使用React完成了Redux項目,我的主要收穫是,應用程序設計中的巨大差異並非必要。 React中使用的大多數方法實際上都可以適應任何其他視圖處理設置。我花了一段時間才意識到這一點,因為我一開始認為我必須做不同的事情,但我最終發現這沒有必要。

然而,不同的是您初始化模塊、存儲的方式,以及組件對整體應用程序狀態的了解程度。概念保持不變,但實現和代碼量完全適合您的需求。

Redux是一個很棒的工具,它有助於以更周到的方式構建您的應用程序。單獨使用,沒有任何視圖庫,一開始可能會非常棘手,但一旦您克服了最初的困惑,就沒有任何東西可以阻止您了。

您如何看待我的方法?您是否獨自使用Redux和不同的視圖處理設置?我很樂意收到您的反饋並在評論中討論它。


如果您想了解更多關於Redux的信息,請查看我們的課程《重寫和測試Redux以解決設計問題》迷你課程。在本課程中,您將構建一個Redux應用程序,該應用程序通過websocket連接接收按主題組織的推文。為了讓您了解即將發生的事情,請查看下面的免費課程。

加載播放器……關於無React的Redux的常見問題解答(FAQ)

使用Redux與React和不使用React的主要區別是什麼?

Redux是一個適用於JavaScript應用程序的可預測狀態容器,可以與任何UI層一起使用。使用Redux與React和不使用React之間的主要區別在於UI層與Redux存儲交互的方式。當與React一起使用時,Redux可以利用React的基於組件的架構及其生命週期方法來自動處理狀態更改時組件的更新。如果沒有React,您需要手動訂閱存儲並在狀態更改時處理UI的更新。

如何在沒有React的情況下處理Redux中的異步操作?

Redux中的異步操作通常使用Redux Thunk或Redux Saga等中間件來處理。這些中間件允許您調度函數(thunk)或更複雜的異步操作(saga),而不是普通的對象。即使沒有React,您仍然可以在Redux存儲中使用這些中間件。您只需要在使用Redux的applyMiddleware函數創建存儲時應用中間件即可。

我可以在沒有React的情況下使用Redux DevTools嗎?

是的,Redux DevTools不依賴於React,可以與任何使用Redux的UI層一起使用。您可以通過在創建Redux存儲時將其添加為中間件來將Redux DevTools集成到您的應用程序中。這將允許您實時檢查應用程序的狀態和操作,即使沒有React。

如何在我的UI組件連接到Redux存儲而無需React?

如果沒有React及其connect函數,您需要手動訂閱Redux存儲並在狀態更改時更新UI組件。您可以使用store.subscribe方法訂閱存儲,該方法採用一個偵聽器函數,該函數將在每次調度操作時被調用。在此偵聽器函數中,您可以使用store.getState獲取存儲的當前狀態並相應地更新UI組件。

我可以將Redux與其他庫或框架(如Vue或Angular)一起使用嗎?

是的,Redux不依賴於React,可以與任何UI層一起使用。對於其他庫和框架(如Vue和Angular),都提供了提供與React的connect函數類似功能的綁定。這些綁定允許您輕鬆地將UI組件連接到Redux存儲,並在狀態更改時處理組件的更新。

如何在沒有React的情況下測試我的Redux代碼?

在沒有React的情況下測試Redux代碼類似於使用React測試它。您可以使用任何JavaScript測試框架(如Jest或Mocha)為您的action creators和reducers創建單元測試。對於測試異步操作,您可以使用模擬存儲來模擬Redux存儲。

如何在沒有React的情況下處理Redux中的副作用?

Redux中的副作用通常使用Redux Thunk或Redux Saga等中間件來處理。這些中間件允許您調度具有副作用的函數或更複雜的異步操作,例如進行API調用。即使沒有React,您仍然可以在Redux存儲中使用這些中間件。

我可以將Redux與純JavaScript一起使用嗎?

是的,Redux可以與純JavaScript一起使用。您可以創建一個Redux存儲,向其調度操作,並僅使用純JavaScript訂閱狀態中的更改。但是,如果沒有像React這樣的庫或框架來處理UI的更新,您需要在狀態更改時手動更新UI組件。

如何在沒有React的情況下構建Redux代碼?

Redux代碼的結構並不取決於您是否使用React。您仍然可以遵循構建Redux代碼的相同最佳實踐,例如將操作、reducers和selectors分離到不同的文件或文件夾中,並以規範化和模塊化的方式組織您的狀態。

我可以在沒有React的情況下使用Redux中間件嗎?

是的,Redux中間件不依賴於React,可以與任何使用Redux的UI層一起使用。 Redux中的中間件用於處理副作用和異步操作,等等。您可以使用Redux的applyMiddleware函數將中間件應用於您的Redux存儲,無論您是否使用React。

以上是沒有反應的redux的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板