Redux 基礎知識,第 4 部分:儲存
Redux 基礎知識,第 4 部分:儲存
- 如何建立 Redux 儲存
- 如何使用儲存庫更新狀態並偵聽更新
- 如何設定儲存庫以擴充其功能
- 如何設定 Redux DevTools Extension 以除錯應用程式
簡介
在 第 3 部分:狀態、動作和簡約函式 中,我們開始撰寫範例待辦事項應用程式。我們列出商業需求、定義應用程式運作所需的狀態結構,並建立一系列動作類型來描述「發生了什麼事」以及與使用者與應用程式互動時可能發生的事件類型相符。我們也撰寫了簡約函式,可以處理更新我們的 state.todos
和 state.filters
區段,並了解如何使用 Redux combineReducers
函式根據應用程式中每個功能的不同「區段簡約函式」建立「根簡約函式」。
現在,是時候將這些部分組合在一起,使用 Redux 應用程式的核心部分:儲存庫。
請注意,本教學課程故意顯示較舊的 Redux 邏輯模式,這些模式需要比使用 Redux Toolkit 的「現代 Redux」模式更多程式碼,而後者是我們教導用於建立 Redux 應用程式的正確方法,目的是說明 Redux 背後的原則和概念。它不是一個可供生產環境使用的專案。
請參閱這些網頁,了解如何使用 Redux Toolkit 的「現代 Redux」
- 完整的「Redux Essentials」教學課程,教導「如何正確使用 Redux」以及將 Redux Toolkit 用於實際應用程式。我們建議所有 Redux 學習者都應該閱讀「Essentials」教學課程!
- Redux 基本原理,第 8 部分:使用 Redux Toolkit 進行現代 Redux,說明如何將先前各節中的低階範例轉換為現代 Redux Toolkit 等效項
Redux Store
Redux store 將構成應用程式的狀態、動作和 reducer 整合在一起。Store 有數項職責
- 在內部儲存目前的應用程式狀態
- 允許透過
store.getState()
存取目前的狀態; - 允許透過
store.dispatch(action)
更新狀態; - 透過
store.subscribe(listener)
註冊監聽器回呼函式; - 透過
store.subscribe(listener)
傳回的unsubscribe
函式處理監聽器的取消註冊。
務必注意,Redux 應用程式中只會有一個 store。當您想要分割資料處理邏輯時,您將使用 reducer 組合 並建立多個 reducer,這些 reducer 可以組合在一起,而不是建立個別的 store。
建立 Store
每個 Redux store 都有一個單一的根 reducer 函式。在上一節中,我們 使用 combineReducers
建立根 reducer 函式。目前在範例應用程式的 src/reducer.js
中定義該根 reducer。讓我們匯入該根 reducer 並建立我們的第一個 store。
Redux 核心函式庫有一個 createStore
API,將建立 store。新增一個名為 store.js
的新檔案,並匯入 createStore
和根 reducer。然後,呼叫 createStore
並傳入根 reducer
import { createStore } from 'redux'
import rootReducer from './reducer'
const store = createStore(rootReducer)
export default store
載入初始狀態
createStore
也可以將 preloadedState
值作為其第二個引數接受。您可以使用此值在建立 store 時新增初始資料,例如包含在伺服器傳送的 HTML 頁面中的值,或儲存在 localStorage
中並在使用者再次造訪頁面時讀回的值,如下所示
import { createStore } from 'redux'
import rootReducer from './reducer'
let preloadedState
const persistedTodosString = localStorage.getItem('todos')
if (persistedTodosString) {
preloadedState = {
todos: JSON.parse(persistedTodosString)
}
}
const store = createStore(rootReducer, preloadedState)
發送動作
現在我們已經建立一個 store,讓我們驗證我們的程式是否運作!即使沒有任何 UI,我們已經可以測試更新邏輯。
在執行這段程式碼之前,請嘗試回到 src/features/todos/todosSlice.js
,並從 initialState
中移除所有範例待辦事項物件,使它成為一個空陣列。這將使這個範例的輸出更容易閱讀。
// Omit existing React imports
import store from './store'
// Log the initial state
console.log('Initial state: ', store.getState())
// {todos: [....], filters: {status, colors}}
// Every time the state changes, log it
// Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() =>
console.log('State after dispatch: ', store.getState())
)
// Now, dispatch some actions
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about reducers' })
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about stores' })
store.dispatch({ type: 'todos/todoToggled', payload: 0 })
store.dispatch({ type: 'todos/todoToggled', payload: 1 })
store.dispatch({ type: 'filters/statusFilterChanged', payload: 'Active' })
store.dispatch({
type: 'filters/colorFilterChanged',
payload: { color: 'red', changeType: 'added' }
})
// Stop listening to state updates
unsubscribe()
// Dispatch one more action to see what happens
store.dispatch({ type: 'todos/todoAdded', payload: 'Try creating a store' })
// Omit existing React rendering logic
請記住,每次我們呼叫 store.dispatch(action)
- 儲存庫會呼叫
rootReducer(state, action)
- 那個根部 reducer 可能會在自身內部呼叫其他切片 reducer,例如
todosReducer(state.todos, action)
- 那個根部 reducer 可能會在自身內部呼叫其他切片 reducer,例如
- 儲存庫會將新的狀態值儲存在內部
- 儲存庫會呼叫所有監聽器訂閱回呼函式
- 如果監聽器有權存取
store
,它現在可以呼叫store.getState()
來讀取最新的狀態值
如果我們查看那個範例的控制台記錄輸出,你可以看到 Redux 狀態在每個動作被派送時是如何變化的
請注意,我們的應用程式沒有記錄任何來自最後一個動作的內容。這是因為我們在呼叫 unsubscribe()
時移除了監聽器回呼函式,所以動作被派送後沒有其他任何動作會執行。
我們在開始撰寫 UI 之前就指定了我們應用程式的行為。這有助於讓我們確信應用程式將按預期執行。
如果你願意,現在可以嘗試為你的 reducer 撰寫測試。因為它們是 純函式,所以測試它們應該很簡單。使用範例 state
和 action
呼叫它們,取得結果,並檢查它是否與你的預期相符
import todosReducer from './todosSlice'
test('Toggles a todo based on id', () => {
const initialState = [{ id: 0, text: 'Test text', completed: false }]
const action = { type: 'todos/todoToggled', payload: 0 }
const result = todosReducer(initialState, action)
expect(result[0].completed).toBe(true)
})
在 Redux 儲存庫內部
深入了解 Redux 儲存庫以了解它的運作方式可能會很有幫助。以下是一個運作中的 Redux 儲存庫的微型範例,大約有 25 行程式碼
function createStore(reducer, preloadedState) {
let state = preloadedState
const listeners = []
function getState() {
return state
}
function subscribe(listener) {
listeners.push(listener)
return function unsubscribe() {
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
function dispatch(action) {
state = reducer(state, action)
listeners.forEach(listener => listener())
}
dispatch({ type: '@@redux/INIT' })
return { dispatch, subscribe, getState }
}
這個 Redux 儲存庫的小版本運作良好,你可以使用它來取代你到目前為止在應用程式中使用的實際 Redux createStore
函式。(試試看,親自體驗!)實際的 Redux 儲存庫實作較長且稍微複雜一些,但大部分都是註解、警告訊息和處理一些特殊情況。
如你所見,這裡的實際邏輯相當簡短
- 商店內部有目前的
state
值和reducer
函式 getState
回傳目前的 state 值subscribe
保留一個監聽器回呼陣列,並回傳一個函式以移除新的回呼dispatch
呼叫 reducer,儲存 state,並執行監聽器- 商店在啟動時會發送一個動作,以使用其 state 初始化 reducer
- 商店 API 是包含
{dispatch, subscribe, getState}
的物件
特別強調其中一個:請注意,getState
只會回傳目前的 state
值。這表示 **預設情況下,沒有任何機制可以防止你意外變更目前的 state 值!** 以下程式碼可以執行,但並不正確
const state = store.getState()
// ❌ Don't do this - it mutates the current state!
state.filters.status = 'Active'
換句話說
- Redux 商店並不會在你呼叫
getState()
時額外複製state
值。它與根 reducer 函式回傳的參考完全相同 - Redux 商店並不會採取任何其他措施來防止意外變更。**可以**在 reducer 內部或商店外部變更 state,你必須隨時小心避免變更。
意外變更的一個常見原因是排序陣列。 呼叫 array.sort()
實際上會變更現有的陣列。如果我們呼叫 const sortedTodos = state.todos.sort()
,我們最終會意外變更真正的商店 state。
在 第 8 部分:現代 Redux 中,我們將了解 Redux Toolkit 如何協助避免 reducer 中的變更,以及偵測和警告 reducer 外部的意外變更。
設定商店
我們已經看到,我們可以將 rootReducer
和 preloadedState
引數傳遞給 createStore
。不過,createStore
也可以接受另一個引數,用於自訂商店的功能並賦予它新的能力。
Redux 商店使用稱為 **商店增強器** 的東西進行自訂。商店增強器就像 createStore
的特殊版本,它會在原始 Redux 商店周圍新增另一層包裝。增強的商店接著可以透過提供它自己的商店 dispatch
、getState
和 subscribe
函式(取代原始函式)來變更商店的行為。
在本教學課程中,我們不會深入探討商店增強器實際運作的方式,我們會專注於如何使用它們。
使用增強器建立商店
我們的專案在 src/exampleAddons/enhancers.js
檔案中提供了兩個小型範例商店增強器
sayHiOnDispatch
:一個增強器,每次發送動作時,都會在主控台記錄'Hi'!
includeMeaningOfLife
:一個增強器,會永遠將欄位meaningOfLife: 42
加入從getState()
傳回的值中
我們先從使用 sayHiOnDispatch
開始。首先,我們會匯入它,並傳遞給 createStore
import { createStore } from 'redux'
import rootReducer from './reducer'
import { sayHiOnDispatch } from './exampleAddons/enhancers'
const store = createStore(rootReducer, undefined, sayHiOnDispatch)
export default store
我們這裡沒有 preloadedState
值,所以我們會傳遞 undefined
作為第二個參數。
接下來,我們試試看發送一個動作
import store from './store'
console.log('Dispatching action')
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
console.log('Dispatch complete')
現在看看主控台。你應該會看到 'Hi!'
記錄在其中,出現在其他兩個記錄陳述之間
sayHiOnDispatch
增強器用它自己的 dispatch
特殊版本包裝了原始的 store.dispatch
函式。當我們呼叫 store.dispatch()
時,我們實際上是在呼叫 sayHiOnDispatch
的包裝函式,它會呼叫原始函式,然後印出「Hi」。
現在,我們試試看加入第二個增強器。我們可以從同一個檔案匯入 includeMeaningOfLife
... 但我們遇到了一個問題。createStore
只接受一個增強器作為它的第三個參數!我們要如何同時傳遞 兩個 增強器?
我們真正需要的是某種方法,可以將 sayHiOnDispatch
增強器和 includeMeaningOfLife
增強器合併成一個單一的合併增強器,然後再傳遞它。
幸運的是,Redux 核心包含 一個 compose
函式,可以用來合併多個增強器。我們在這裡使用它
import { createStore, compose } from 'redux'
import rootReducer from './reducer'
import {
sayHiOnDispatch,
includeMeaningOfLife
} from './exampleAddons/enhancers'
const composedEnhancer = compose(sayHiOnDispatch, includeMeaningOfLife)
const store = createStore(rootReducer, undefined, composedEnhancer)
export default store
現在我們可以看到,如果我們使用儲存庫會發生什麼事
import store from './store'
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
// log: 'Hi!'
console.log('State after dispatch: ', store.getState())
// log: {todos: [...], filters: {status, colors}, meaningOfLife: 42}
而記錄的輸出看起來像這樣
因此,我們可以看到兩個增強器同時修改了儲存庫的行為。sayHiOnDispatch
改變了 dispatch
的運作方式,而 includeMeaningOfLife
改變了 getState
的運作方式。
儲存庫增強器是一種修改儲存庫的強大方法,而且幾乎所有的 Redux 應用程式在設定儲存庫時都會包含至少一個增強器。
如果你沒有任何要傳入的 preloadedState
,你可以將 enhancer
傳遞為第二個參數
const store = createStore(rootReducer, storeEnhancer)
中間件
增強器很強大,因為它們可以覆寫或取代儲存庫的任何方法:dispatch
、getState
和 subscribe
。
但是,很多時候,我們只需要自訂 dispatch
的行為。如果有一種方法可以在 dispatch
執行時加入一些自訂行為,那就太好了。
Redux 使用一種稱為中間件的特殊外掛程式,讓我們可以自訂 dispatch
函式。
如果您曾經使用過 Express 或 Koa 等函式庫,您可能已經熟悉新增中介軟體以自訂行為的概念。在這些架構中,中介軟體是您可以在架構接收請求和架構產生回應之間放置的一些程式碼。例如,Express 或 Koa 中介軟體可能會新增 CORS 標頭、記錄、壓縮等等。中介軟體最棒的功能是它可以在鏈中組合。您可以在單一專案中使用多個獨立的第三方中介軟體。
Redux 中介軟體解決的問題與 Express 或 Koa 中介軟體不同,但在概念上類似。Redux 中介軟體在傳送動作和動作到達 reducer 的那一刻之間提供第三方延伸點。人們使用 Redux 中介軟體進行記錄、錯誤回報、與非同步 API 通訊、路由等等。
首先,我們將了解如何將中介軟體新增到儲存,然後我們將展示如何撰寫您自己的中介軟體。
使用中介軟體
我們已經看到您可以使用儲存增強器自訂 Redux 儲存。Redux 中介軟體實際上是在 Redux 內建的一個非常特別的儲存增強器之上實作的,稱為applyMiddleware
。
由於我們已經知道如何將增強器新增到我們的儲存,我們現在應該可以這樣做。我們將從 applyMiddleware
本身開始,並將新增三個已包含在此專案中的範例中介軟體。
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './reducer'
import { print1, print2, print3 } from './exampleAddons/middleware'
const middlewareEnhancer = applyMiddleware(print1, print2, print3)
// Pass enhancer as the second arg, since there's no preloadedState
const store = createStore(rootReducer, middlewareEnhancer)
export default store
正如它們的名稱所示,每一個這些中介軟體會在傳送動作時列印一個數字。
如果我們現在傳送,會發生什麼事?
import store from './store'
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
// log: '1'
// log: '2'
// log: '3'
我們可以在主控台中看到輸出
那它是如何運作的?
中介軟體在儲存的 dispatch
方法周圍形成一個管線。當我們呼叫 store.dispatch(action)
時,我們實際上是在呼叫管線中的第一個中介軟體。當該中介軟體看到動作時,它可以執行任何它想要做的事。通常,中介軟體會檢查動作是否為它關心的特定類型,就像 reducer 會做的那樣。如果是正確的類型,中介軟體可能會執行一些自訂邏輯。否則,它會將動作傳遞給管線中的下一個中介軟體。
與 reducer 不同,middleware 可以有內部的副作用,包括逾時和其他非同步邏輯。
在這種情況下,動作會傳遞到
print1
middleware(我們將其視為store.dispatch
)print2
middlewareprint3
middleware- 原始的
store.dispatch
store
內部的根 reducer
由於這些都是函式呼叫,因此它們都會從該呼叫堆疊傳回。因此,print1
middleware 是第一個執行的,也是最後一個完成的。
撰寫自訂 middleware
我們也可以撰寫自己的 middleware。您可能不需要一直這麼做,但自訂 middleware 是為 Redux 應用程式新增特定行為的絕佳方式。
Redux middleware 被寫成一系列三個巢狀函式。讓我們看看該模式的樣貌。我們將從嘗試使用 function
關鍵字撰寫此 middleware 開始,這樣可以更清楚地了解發生了什麼事
// Middleware written as ES5 functions
// Outer function:
function exampleMiddleware(storeAPI) {
return function wrapDispatch(next) {
return function handleAction(action) {
// Do anything here: pass the action onwards with next(action),
// or restart the pipeline with storeAPI.dispatch(action)
// Can also use storeAPI.getState() here
return next(action)
}
}
}
讓我們分解這三個函式執行什麼動作以及它們的參數為何。
exampleMiddleware
:外部函式實際上是「middleware」本身。它將由applyMiddleware
呼叫,並接收包含 store 的{dispatch, getState}
函式的storeAPI
物件。這些是實際上是 store 一部分的相同dispatch
和getState
函式。如果您呼叫此dispatch
函式,它會將動作傳送至 middleware 管線的開始。這只會呼叫一次。wrapDispatch
:中間函式接收稱為next
的函式作為其參數。此函式實際上是管線中的下一個 middleware。如果此 middleware 是序列中的最後一個,則next
實際上是原始的store.dispatch
函式。呼叫next(action)
會將動作傳遞至管線中的下一個 middleware。這也只會呼叫一次handleAction
:最後,內部函式接收目前的action
作為其引數,並會在每次派送 action 時呼叫。
你可以為這些中間件函式給予任何你想要的命名,但使用這些命名來記住每個函式的功能會有所幫助。
- 外部:
someCustomMiddleware
(或任何你稱呼中間件的名稱) - 中間:
wrapDispatch
- 內部:
handleAction
由於這些是正常的函式,我們也可以使用 ES2015 箭頭函式來撰寫它們。這讓我們可以寫得更簡短,因為箭頭函式不需要有 return
陳述式,但如果你還不熟悉箭頭函式和隱含回傳,它也可能有點難讀。
以下是用箭頭函式撰寫的相同範例
const anotherExampleMiddleware = storeAPI => next => action => {
// Do something in here, when each action is dispatched
return next(action)
}
我們仍然將這三個函式巢狀在一起,並回傳每個函式,但隱含回傳讓它更簡短。
你的第一個自訂中間件
假設我們想要在應用程式中新增一些記錄。我們希望在派送每個 action 時在主控台中看到其內容,而且我們希望在 action 已由 reducer 處理後看到狀態是什麼。
這些範例中間件並非實際待辦事項應用程式的一部分,但你可以嘗試將它們新增到你的專案中,看看使用它們時會發生什麼事。
我們可以撰寫一個小型中間件,它會將該資訊記錄到主控台中
const loggerMiddleware = storeAPI => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', storeAPI.getState())
return result
}
每當派送 action 時
handleAction
函式的第一部分會執行,而且我們會印出'dispatching'
- 我們將 action 傳遞給
next
區段,它可能是另一個中間件或實際的store.dispatch
- 最後 reducer 會執行,而且狀態會更新,而
next
函式會回傳 - 我們現在可以呼叫
storeAPI.getState()
並查看新的狀態是什麼 - 我們透過回傳來自
next
中間件的任何result
值來完成
任何中間件都可以回傳任何值,而且管線中第一個中間件的回傳值會在你呼叫 store.dispatch()
時實際回傳。例如
const alwaysReturnHelloMiddleware = storeAPI => next => action => {
const originalResult = next(action)
// Ignore the original result, return something else
return 'Hello!'
}
const middlewareEnhancer = applyMiddleware(alwaysReturnHelloMiddleware)
const store = createStore(rootReducer, middlewareEnhancer)
const dispatchResult = store.dispatch({ type: 'some/action' })
console.log(dispatchResult)
// log: 'Hello!'
我們再試一個範例。中間件通常會尋找特定的 action,然後在派送該 action 時執行某些動作。中間件也有能力在內部執行非同步邏輯。我們可以撰寫一個中間件,當它看到某個 action 時,會在延遲後印出一些內容
const delayedMessageMiddleware = storeAPI => next => action => {
if (action.type === 'todos/todoAdded') {
setTimeout(() => {
console.log('Added a new todo: ', action.payload)
}, 1000)
}
return next(action)
}
此中間件會尋找「todo 已新增」動作。每次看到一個動作,它會設定一個 1 秒計時器,然後將動作的有效負載列印到主控台。
中間件使用案例
那麼,我們可以用中間件做什麼?很多事情!
當中間件看到已發送的動作時,它可以做任何它想做的事
- 將某些內容記錄到主控台
- 設定逾時
- 執行非同步 API 呼叫
- 修改動作
- 暫停動作,甚至完全停止動作
以及任何你能想到的事。
特別是,中間件旨在包含具有副作用的邏輯。此外,中間件可以修改 dispatch
以接受不是純粹動作物件的事物。我們將在 第 6 部分:非同步邏輯 中更詳細地討論這兩者。
Redux DevTools
最後,還有一件非常重要的事情要說明,與設定儲存庫有關。
Redux 的設計宗旨是讓您更容易了解您的狀態隨著時間的推移何時、何地、為何以及如何改變。作為其中的一部分,Redux 被建構為支援使用 Redux DevTools,這是一個附加元件,可讓您看到已發送動作的記錄、這些動作包含的內容,以及每個已發送動作後狀態如何改變。
Redux DevTools UI 可用作瀏覽器擴充功能,適用於 Chrome 和 Firefox。如果您尚未將其新增到瀏覽器,請立即進行。
安裝完畢後,開啟瀏覽器的 DevTools 視窗。您現在應該會在那裡看到一個新的「Redux」標籤。它目前還沒有任何作用,我們必須先設定它才能與 Redux 儲存庫通訊。
將 DevTools 新增到儲存庫
安裝擴充功能後,我們需要設定儲存庫,以便 DevTools 可以看到其內部發生了什麼事。DevTools 需要新增一個特定的儲存庫增強器才能做到這一點。
Redux DevTools Extension 文件 中有一些關於如何設定儲存庫的說明,但列出的步驟有點複雜。不過,有一個名為 redux-devtools-extension
的 NPM 套件可以處理複雜的部分。該套件匯出一個專門的 composeWithDevTools
函式,我們可以使用它來取代原始的 Redux compose
函式。
以下是其外觀
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducer'
import { print1, print2, print3 } from './exampleAddons/middleware'
const composedEnhancer = composeWithDevTools(
// EXAMPLE: Add whatever middleware you actually want to use here
applyMiddleware(print1, print2, print3)
// other store enhancers if any
)
const store = createStore(rootReducer, composedEnhancer)
export default store
確保在匯入儲存庫後,index.js
仍會發送動作。現在,開啟瀏覽器 DevTools 視窗中的 Redux DevTools 標籤。您應該會看到類似以下內容的畫面
左側有一個已發送動作的清單。如果我們按一下其中一個動作,右側窗格會顯示幾個標籤
- 該動作物件的內容
- Redux 狀態在 reducer 執行後的完整樣貌
- 此狀態與先前狀態的差異
- 如果啟用,會顯示功能堆疊追蹤,追溯回最初呼叫
store.dispatch()
的程式碼行
在我們發送「新增待辦事項」動作後,「狀態」和「差異」索引標籤會顯示如下內容
這些是非常強大的工具,可以幫助我們除錯應用程式,並了解內部確切發生的情況。
你學到的內容
正如你所見,儲存是每個 Redux 應用程式的核心部分。儲存包含狀態並透過執行 reducer 來處理動作,而且可以自訂以新增其他行為。
讓我們看看我們的範例應用程式現在的樣貌
此外,以下是我們在本節中涵蓋的內容
- Redux 應用程式始終只有一個儲存
- 儲存是使用 Redux
createStore
API 建立的 - 每個儲存都有一個單一的根 reducer 函式
- 儲存是使用 Redux
- 儲存有三個主要方法
getState
傳回目前的狀態dispatch
將動作傳送給 reducer 以更新狀態subscribe
接收一個監聽器回呼,每次發送動作時都會執行
- 儲存增強器讓我們可以在建立儲存時自訂儲存
- 增強器會包裝儲存,並可以覆寫其方法
createStore
接受一個增強器作為引數- 可以使用
compose
API 將多個增強器合併在一起
- Middleware 是自訂儲存的主要方式
- Middleware 是使用
applyMiddleware
增強器新增的 - Middleware 是寫在彼此內部的三個巢狀函式
- 每次發送動作時都會執行 Middleware
- 中間件可以在內部產生副作用
- Middleware 是使用
- Redux DevTools 讓你可以隨著時間推移查看應用程式中有哪些變更
- DevTools 擴充功能可以安裝在你的瀏覽器中
- 儲存區需要使用
composeWithDevTools
加入 DevTools 增強功能 - DevTools 會顯示隨著時間推移而發出的動作和狀態變更
接下來是什麼?
我們現在有一個可用的 Redux 儲存區,它可以執行我們的 reducer,並在我們發出動作時更新狀態。
不過,每個應用程式都需要一個使用者介面來顯示資料並讓使用者執行一些有用的操作。在 第 5 部分:使用者介面和 React 中,我們將了解 Redux 儲存區如何與使用者介面搭配使用,特別是了解 Redux 如何與 React 搭配使用。