跳至主要內容

設定你的儲存庫

「Redux 基礎」教學 中,我們透過建立一個範例待辦事項清單應用程式,介紹了 Redux 的基本概念。其中,我們討論了 如何建立和設定 Redux 儲存庫

現在,我們將探討如何自訂儲存庫以新增額外功能。我們將從 「Redux 基礎」第 5 部分:UI 和 React 的原始程式碼開始。您可以在 Github 上的範例應用程式儲存庫 中查看本教學階段的原始程式碼,或透過 CodeSandbox 在瀏覽器中查看

建立儲存庫

首先,讓我們看看我們建立儲存庫的原始 index.js 檔案

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
import App from './components/App'

const store = createStore(rootReducer)

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

在此程式碼中,我們將簡約器傳遞給 Redux 的 createStore 函式,它會傳回一個 store 物件。然後,我們將此物件傳遞給 react-reduxProvider 元件,該元件會呈現在元件樹的最上方。

這樣可以確保我們透過 react-reduxconnect 在應用程式中連線到 Redux 時,儲存庫會提供給我們的元件使用。

擴充 Redux 功能

大多數應用程式透過新增中間件或儲存庫增強器來擴充 Redux 儲存庫的功能(注意:中間件很常見,增強器較不常見)。中間件會為 Redux 的 dispatch 函式新增額外功能;增強器會為 Redux 儲存庫新增額外功能。

我們將新增兩個中間件和一個增強器

  • redux-thunk 中間件,它允許簡單地非同步使用 dispatch。
  • 一個會記錄已 dispatch 的動作和產生的新狀態的中間件。
  • 一個會記錄簡約器處理每個動作所花費時間的增強器。

安裝 redux-thunk

npm install redux-thunk

middleware/logger.js

const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}

export default logger

enhancers/monitorReducer.js

const round = number => Math.round(number * 100) / 100

const monitorReducerEnhancer =
createStore => (reducer, initialState, enhancer) => {
const monitoredReducer = (state, action) => {
const start = performance.now()
const newState = reducer(state, action)
const end = performance.now()
const diff = round(end - start)

console.log('reducer process time:', diff)

return newState
}

return createStore(monitoredReducer, initialState, enhancer)
}

export default monitorReducerEnhancer

讓我們將它們新增到現有的 index.js 中。

  • 首先,我們需要匯入 redux-thunk、我們的 loggerMiddlewaremonitorReducerEnhancer,以及 Redux 提供的兩個額外函式:applyMiddlewarecompose

  • 然後,我們使用 applyMiddleware 建立一個儲存庫增強器,它會將我們的 loggerMiddlewarethunkMiddleware 套用到儲存庫的 dispatch 函式。

  • 接下來,我們使用 compose 將新的 middlewareEnhancermonitorReducerEnhancer 組合為一個函式。

    這是必要的,因為你只能將一個 enhancer 傳遞到 createStore。若要使用多個 enhancer,你必須先將它們組合成一個更大的 enhancer,如本範例所示。

  • 最後,我們將這個新的 composedEnhancers 函式作為第三個參數傳遞到 createStore注意:第二個參數(我們將忽略它)讓你將預載狀態載入儲存區。

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { applyMiddleware, createStore, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from './reducers'
import loggerMiddleware from './middleware/logger'
import monitorReducerEnhancer from './enhancers/monitorReducer'
import App from './components/App'

const middlewareEnhancer = applyMiddleware(loggerMiddleware, thunkMiddleware)
const composedEnhancers = compose(middlewareEnhancer, monitorReducerEnhancer)

const store = createStore(rootReducer, undefined, composedEnhancers)

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

此方法的問題

雖然此程式碼有效,但對於一般應用程式而言並不理想。

大多數應用程式使用多個中間件,且每個中間件通常需要一些初始設定。新增到 index.js 的額外雜訊會很快地讓它難以維護,因為邏輯沒有清楚地組織。

解決方案:configureStore

解決此問題的方法是建立一個新的 configureStore 函式,它封裝我們的儲存區建立邏輯,然後可以將它放在自己的檔案中以簡化擴充性。

最終目標是讓我們的 index.js 如下所示

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import configureStore from './configureStore'

const store = configureStore()

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

所有與設定儲存區相關的邏輯(包括匯入 reducer、中間件和 enhancer)都在一個專用的檔案中處理。

為達成此目的,configureStore 函式如下所示

import { applyMiddleware, compose, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'

import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'

export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)

const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = compose(...enhancers)

const store = createStore(rootReducer, preloadedState, composedEnhancers)

return store
}

此函式遵循上述相同步驟,並將一些邏輯拆分出來以準備擴充,這將讓未來更容易新增更多內容

  • middlewaresenhancers 都定義為陣列,與使用它們的函式分開。

    這讓我們可以根據不同的條件輕鬆地新增更多中間件或 enhancer。

    例如,只在開發模式下新增一些中間件很常見,這很容易透過在 if 陳述式中推送到 middlewares 陣列來達成

    if (process.env.NODE_ENV === 'development') {
    middlewares.push(secretMiddleware)
    }
  • preloadedState 變數傳遞到 createStore,以防我們稍後想新增它。

這也讓我們的 createStore 函式更容易理解,每個步驟都清楚地分開,這使得正在發生的事情更為明顯。

整合 devtools 擴充功能

另一個你可能想新增到應用程式的常見功能是 redux-devtools-extension 整合。

此擴充功能是一組工具,可讓你完全控制 Redux 儲存,讓你能夠檢查和重播動作、探索不同時間的狀態、直接將動作傳送至儲存,以及更多功能。按一下這裡閱讀更多關於可用功能的資訊。

有許多方法可以整合擴充功能,但我們將使用最方便的選項。

首先,我們透過 npm 安裝套件

npm install --save-dev redux-devtools-extension

接下來,我們移除從 redux 匯入的 compose 函式,並用從 redux-devtools-extension 匯入的新 composeWithDevTools 函式取代它。

最後的程式碼如下所示

import { applyMiddleware, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'

import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'

export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)

const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = composeWithDevTools(...enhancers)

const store = createStore(rootReducer, preloadedState, composedEnhancers)

return store
}

這樣就完成了!

如果我們現在透過已安裝 devtools 擴充功能的瀏覽器拜訪我們的應用程式,我們可以使用強大的新工具來探索和除錯。

熱重載

另一個強大的工具可以讓開發流程更直觀,那就是熱重載,這表示在不重新啟動整個應用程式的狀況下更換程式碼片段。

例如,考慮一下當你執行應用程式、與它互動一段時間,然後決定變更其中一個 reducer 時會發生什麼事。通常,當你進行這些變更時,應用程式會重新啟動,將 Redux 狀態還原為其初始值。

啟用熱模組重載後,只會重新載入你變更的 reducer,讓你可以在每次變更程式碼時重設狀態。這讓開發流程快上許多。

我們將熱重載新增至 Redux reducer 和 React 元件。

首先,讓我們將它新增至 configureStore 函式

import { applyMiddleware, compose, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'

import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'

export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)

const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = compose(...enhancers)

const store = createStore(rootReducer, preloadedState, composedEnhancers)

if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
}

return store
}

新的程式碼包在 if 陳述式中,因此它只會在我們的應用程式不在生產模式時執行,而且只有在 module.hot 功能可用時執行。

Webpack 和 Parcel 等套件管理工具支援 module.hot.accept 方法,用於指定應熱重載哪個模組,以及模組變更時應執行哪些動作。在此情況下,我們會監控 ./reducers 模組,並在它變更時將更新的 rootReducer 傳遞給 store.replaceReducer 方法。

我們也會在 index.js 中使用相同的模式,熱重載 React 元件的任何變更

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import configureStore from './configureStore'

const store = configureStore()

const renderApp = () =>
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./components/App', renderApp)
}

renderApp()

這裡唯一的額外變更,是我們已將應用程式的渲染封裝到新的 renderApp 函式中,我們現在呼叫它來重新渲染應用程式。

使用 Redux Toolkit 簡化設定

Redux 核心函式庫故意不帶有意見。它讓您決定如何處理所有事情,例如儲存設定、狀態包含的內容,以及您要如何建置簡化器。

在某些情況下,這很好,因為它提供了彈性,但並不總是需要這種彈性。有時我們只需要最簡單的方法來開始,並具有一些開箱即用的良好預設行為。

Redux Toolkit 套件旨在簡化多個常見的 Redux 使用案例,包括儲存設定。讓我們看看它如何協助改善儲存設定程序。

Redux Toolkit 包含一個預先建置的 configureStore 函式,就像先前範例中顯示的那樣。

使用它的最快方法,就是傳遞根簡化器函式

import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducers'

const store = configureStore({
reducer: rootReducer
})

export default store

請注意,它接受包含命名參數的物件,以更清楚地說明您傳遞的內容。

預設情況下,Redux Toolkit 的 configureStore

  • 呼叫 applyMiddleware,並傳入 預設中介軟體清單,包括 redux-thunk,以及一些僅限開發的中介軟體,用於偵測常見錯誤,例如狀態突變
  • 呼叫 composeWithDevTools,以設定 Redux DevTools 擴充功能

以下是使用 Redux Toolkit 的熱重載範例

import { configureStore } from '@reduxjs/toolkit'

import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'

export default function configureAppStore(preloadedState) {
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware().prepend(loggerMiddleware),
preloadedState,
enhancers: [monitorReducersEnhancer]
})

if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
}

return store
}

這絕對簡化了一些設定程序。

後續步驟

現在您已知道如何封裝儲存設定,以簡化維護,您可以 查看 Redux Toolkit configureStore API,或更仔細地查看 Redux 生態系統中可用的擴充功能