Redux 快速指南
Redux - 概述
Redux 是一個用於 JavaScript 應用的可預測狀態容器。隨著應用程式的增長,保持其組織性和維護資料流變得越來越困難。Redux 透過使用名為 Store 的單個全域性物件管理應用程式的狀態來解決此問題。Redux 的基本原則有助於在整個應用程式中保持一致性,從而使除錯和測試更容易。
更重要的是,它提供了即時程式碼編輯以及時間旅行偵錯程式。它可以靈活地與任何檢視層一起使用,例如 React、Angular、Vue 等。
Redux 的原則
Redux 的可預測性由以下三個最重要的原則決定:
單一資料來源
整個應用程式的狀態儲存在單個 store 中的一個物件樹內。由於整個應用程式狀態儲存在一個單一樹中,因此它使除錯更容易,開發速度更快。
狀態是隻讀的
更改狀態的唯一方法是發出一個 action(動作),一個描述發生了什麼的物件。這意味著沒有人可以直接更改應用程式的狀態。
更改使用純函式進行
為了指定如何透過 action 轉換狀態樹,您需要編寫純 reducers(化簡器)。Reducer 是狀態修改發生的核心位置。Reducer 是一個函式,它接受 state(狀態)和 action(動作)作為引數,並返回一個新更新的狀態。
Redux - 安裝
在安裝 Redux 之前,**我們必須安裝 Nodejs 和 NPM**。以下說明將幫助您安裝它。如果您已經在裝置上安裝了 Nodejs 和 NPM,則可以跳過這些步驟。
訪問 https://nodejs.com.tw/ 並安裝程式包檔案。
執行安裝程式,按照說明操作並接受許可協議。
重啟您的裝置以執行它。
您可以透過開啟命令提示符並鍵入 node -v 來檢查安裝是否成功。這將向您顯示系統中 Node 的最新版本。
要檢查 npm 是否已成功安裝,您可以鍵入 npm –v,它將返回最新的 npm 版本。
要安裝 redux,您可以按照以下步驟操作:
在您的命令提示符中執行以下命令以安裝 Redux。
npm install --save redux
要在 react 應用程式中使用 Redux,您需要安裝以下附加依賴項:
npm install --save react-redux
要為 Redux 安裝開發者工具,您需要安裝以下依賴項:
在您的命令提示符中執行以下命令以安裝 Redux 開發者工具。
npm install --save-dev redux-devtools
如果您不想安裝 Redux 開發者工具並將其整合到您的專案中,您可以為 Chrome 和 Firefox 安裝**Redux DevTools 擴充套件程式**。
Redux - 核心概念
讓我們假設我們的應用程式狀態由一個名為**initialState**的普通物件描述,如下所示:
const initialState = {
isLoading: false,
items: [],
hasError: false
};
應用程式中的任何程式碼片段都不能更改此狀態。要更改狀態,您需要分派一個 action。
什麼是 action(動作)?
action 是一個普通物件,它描述了導致更改的意圖,並具有 type 屬性。它必須具有一個 type 屬性,該屬性指示正在執行的動作型別。action 的命令如下:
return {
type: 'ITEMS_REQUEST', //action type
isLoading: true //payload information
}
action 和 state 透過名為 Reducer 的函式聯絡在一起。分派 action 的目的是引起更改。此更改由 reducer 執行。Reducer 是更改 Redux 中狀態的唯一方法,使其更易於預測、集中和除錯。處理“ITEMS_REQUEST”action 的 reducer 函式如下:
const reducer = (state = initialState, action) => { //es6 arrow function
switch (action.type) {
case 'ITEMS_REQUEST':
return Object.assign({}, state, {
isLoading: action.isLoading
})
default:
return state;
}
}
Redux 有一個儲存應用程式狀態的單個 store。如果您想根據資料處理邏輯拆分程式碼,則應開始拆分 reducers 而不是 Redux 中的 store。
我們將在本教程的後面討論如何拆分 reducers 並將其與 store 結合使用。
Redux 元件如下:
Redux - 資料流
Redux 遵循單向資料流。這意味著您的應用程式資料將遵循單向繫結資料流。隨著應用程式的增長和變得複雜,如果您無法控制應用程式的狀態,則很難重現問題並新增新功能。
Redux 透過限制如何以及何時更新狀態來降低程式碼的複雜性。這樣,管理更新後的狀態就很容易了。我們已經瞭解了 Redux 的三個原則中的限制。下圖將幫助您更好地理解 Redux 資料流:
當用戶與應用程式互動時,將分派一個 action。
使用當前狀態和已分派的 action 呼叫根 reducer 函式。根 reducer 可以將任務分配給較小的 reducer 函式,最終返回一個新狀態。
store 透過執行其回撥函式來通知檢視。
檢視可以檢索更新後的狀態並重新渲染。
Redux - Store(資料倉庫)
store 在 Redux 中是一個不可變的物件樹。store 是一個狀態容器,它儲存應用程式的狀態。Redux 在您的應用程式中只能擁有一個 store。每當在 Redux 中建立 store 時,您都需要指定 reducer。
讓我們看看如何使用 Redux 中的**createStore**方法建立一個 store。需要從支援 store 建立過程的 Redux 庫匯入 createStore 包,如下所示:
import { createStore } from 'redux';
import reducer from './reducers/reducer'
const store = createStore(reducer);
createStore 函式可以有三個引數。以下是語法:
createStore(reducer, [preloadedState], [enhancer])
reducer 是一個返回應用程式下一個狀態的函式。preloadedState 是一個可選引數,是應用程式的初始狀態。enhancer 也是一個可選引數。它將幫助您使用第三方功能增強 store。
store 有三個重要的 methods(方法),如下所示:
getState
它幫助您檢索 Redux store 的當前狀態。
getState 的語法如下:
store.getState()
dispatch
它允許您分派一個 action 來更改應用程式中的狀態。
dispatch 的語法如下:
store.dispatch({type:'ITEMS_REQUEST'})
subscribe
它幫助您註冊一個回撥函式,Redux store 將在分派 action 後呼叫該函式。一旦 Redux 狀態更新,檢視將自動重新渲染。
dispatch 的語法如下:
store.subscribe(()=>{ console.log(store.getState());})
請注意,subscribe 函式返回一個用於取消訂閱偵聽器的函式。要取消訂閱偵聽器,我們可以使用以下程式碼:
const unsubscribe = store.subscribe(()=>{console.log(store.getState());});
unsubscribe();
Redux - Actions(動作)
根據 Redux 官方文件,action 是 store 的唯一資訊來源。它承載著從您的應用程式到 store 的資訊負載。
如前所述,action 是必須具有 type 屬性以指示所執行 action 型別的普通 JavaScript 物件。它告訴我們發生了什麼。型別應在您的應用程式中定義為字串常量,如下所示:
const ITEMS_REQUEST = 'ITEMS_REQUEST';
除了此 type 屬性外,action 物件的結構完全取決於開發者。建議使 action 物件儘可能輕量,並且只傳遞必要的資訊。
要導致 store 中的任何更改,您需要首先使用 store.dispatch() 函式分派一個 action。action 物件如下所示:
{ type: GET_ORDER_STATUS , payload: {orderId,userId } }
{ type: GET_WISHLIST_ITEMS, payload: userId }
Actions Creators(動作建立器)
Actions Creators 是封裝 action 物件建立過程的函式。這些函式只是返回一個普通的 Js 物件,它是一個 action。它促進了編寫簡潔的程式碼並有助於實現可重用性。
讓我們瞭解一下 action creator,它允許您分派一個 action,“ITEMS_REQUEST”,該 action 向伺服器請求產品專案列表資料。同時,在“ITEMS_REQUEST”action 型別中,reducer 將 isLoading 狀態設定為 true,以指示專案正在載入,並且仍未從伺服器接收資料。
最初,在**initialState**物件中,isLoading 狀態為 false,假設沒有任何內容正在載入。當瀏覽器接收到資料時,在相應的 reducer 中,“ITEMS_REQUEST_SUCCESS”action 型別將 isLoading 狀態返回為 false。此狀態可用作 react 元件中的 prop,在請求資料的過程中在頁面上顯示載入程式/訊息。action creator 如下所示:
const ITEMS_REQUEST = ‘ITEMS_REQUEST’ ;
const ITEMS_REQUEST_SUCCESS = ‘ITEMS_REQUEST_SUCCESS’ ;
export function itemsRequest(bool,startIndex,endIndex) {
let payload = {
isLoading: bool,
startIndex,
endIndex
}
return {
type: ITEMS_REQUEST,
payload
}
}
export function itemsRequestSuccess(bool) {
return {
type: ITEMS_REQUEST_SUCCESS,
isLoading: bool,
}
}
要呼叫 dispatch 函式,您需要將 action 作為引數傳遞給 dispatch 函式。
dispatch(itemsRequest(true,1, 20)); dispatch(itemsRequestSuccess(false));
您可以直接使用 store.dispatch() 分派 action。但是,您更有可能使用名為**connect()**的 react-Redux 輔助方法訪問它。您還可以使用**bindActionCreators()**方法將許多 action creators 與 dispatch 函式繫結。
Redux - 純函式
函式是一個過程,它接受稱為引數的輸入,併產生一些稱為返回值的輸出。如果函式遵守以下規則,則稱為純函式:
對於相同的引數,函式返回相同的結果。
它的計算沒有副作用,即它不會更改輸入資料。
不更改區域性和全域性變數。
它不依賴於外部狀態,例如全域性變數。
讓我們以一個函式為例,該函式返回傳遞給函式的輸入值的二倍。通常,它寫成 f(x) => x*2。如果使用引數值 2 呼叫函式,則輸出將為 4,f(2) => 4。
讓我們用 JavaScript 編寫函式的定義,如下所示:
const double = x => x*2; // es6 arrow function console.log(double(2)); // 4
此處,double 是一個純函式。
根據 Redux 中的三個原則,必須透過純函式(即 Redux 中的 reducer)進行更改。現在,問題出現了,為什麼 reducer 必須是純函式。
假設您想分派一個型別為“ADD_TO_CART_SUCCESS”的 action,透過單擊“新增到購物車”按鈕將商品新增到您的購物車應用程式。
讓我們假設 reducer 正在將商品新增到您的購物車,如下所示:
const initialState = {
isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => { //es6 arrow function
switch (action.type) {
case 'ADD_TO_CART_SUCCESS' :
state.isAddedToCart = !state.isAddedToCart; //original object altered
return state;
default:
return state;
}
}
export default addToCartReducer ;
讓我們假設**isAddedToCart**是 state 物件上的一個屬性,它允許您透過返回布林值“true 或 false”來決定何時停用商品的“新增到購物車”按鈕。這可以防止使用者多次新增同一產品。現在,我們不是返回一個新物件,而是像上面那樣更改 state 上的 isAddedToCart 屬性。現在,如果我們嘗試將商品新增到購物車,則不會發生任何事情。“新增到購物車”按鈕不會被停用。
此行為的原因如下:
Redux 透過兩個物件的記憶體位置來比較舊物件和新物件。如果發生任何更改,它期望 reducer 返回一個新物件。如果沒有任何更改,它也期望返回舊物件。在這種情況下,它是相同的。由於這個原因,Redux 假設沒有任何事情發生。
因此,reducer 在 Redux 中必須是一個純函式。以下是在不進行更改的情況下編寫它的方法:
const initialState = {
isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => { //es6 arrow function
switch (action.type) {
case 'ADD_TO_CART_SUCCESS' :
return {
...state,
isAddedToCart: !state.isAddedToCart
}
default:
return state;
}
}
export default addToCartReducer;
Redux - Reducers(化簡器)
Reducers 是 Redux 中的純函式。純函式是可預測的。Reducers 是更改 Redux 中狀態的唯一方法。它是您可以編寫邏輯和計算的唯一地方。Reducer 函式將接受應用程式的先前狀態和正在分派的 action,計算下一個狀態並返回新物件。
以下幾件事永遠不應該在 reducer 中執行:
- 更改函式引數
- API 呼叫和路由邏輯
- 呼叫非純函式,例如 Math.random()
以下是 reducer 的語法:
(state,action) => newState
讓我們繼續在 action creators 模組中討論的顯示網頁上產品專案列表的示例。讓我們在下面看看如何編寫其 reducer。
const initialState = {
isLoading: false,
items: []
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ITEMS_REQUEST':
return Object.assign({}, state, {
isLoading: action.payload.isLoading
})
case ‘ITEMS_REQUEST_SUCCESS':
return Object.assign({}, state, {
items: state.items.concat(action.items),
isLoading: action.isLoading
})
default:
return state;
}
}
export default reducer;
首先,如果您沒有將狀態設定為“initialState”,Redux 會使用未定義的狀態呼叫 reducer。在這個程式碼示例中,JavaScript 的 concat() 函式用於“ITEMS_REQUEST_SUCCESS”,它不會更改現有陣列;而是返回一個新陣列。
這樣,您可以避免狀態突變。切勿直接寫入狀態。“ITEMS_REQUEST”中,我們必須根據接收到的 action 設定狀態值。
前面已經討論過,我們可以在 reducer 中編寫我們的邏輯,並可以基於邏輯資料對其進行拆分。讓我們看看在處理大型應用程式時,如何拆分 reducer 並將它們組合在一起作為根 reducer。
假設,我們想設計一個網頁,使用者可以在其中訪問產品訂單狀態並檢視願望清單資訊。我們可以將邏輯分離到不同的 reducer 檔案中,並使它們獨立工作。讓我們假設 GET_ORDER_STATUS action 被分派以獲取與某個訂單 ID 和使用者 ID 對應的訂單狀態。
/reducer/orderStatusReducer.js
import { GET_ORDER_STATUS } from ‘../constants/appConstant’;
export default function (state = {} , action) {
switch(action.type) {
case GET_ORDER_STATUS:
return { ...state, orderStatusData: action.payload.orderStatus };
default:
return state;
}
}
類似地,假設 GET_WISHLIST_ITEMS action 被分派以獲取使用者相對於使用者的願望清單資訊。
/reducer/getWishlistDataReducer.js
import { GET_WISHLIST_ITEMS } from ‘../constants/appConstant’;
export default function (state = {}, action) {
switch(action.type) {
case GET_WISHLIST_ITEMS:
return { ...state, wishlistData: action.payload.wishlistData };
default:
return state;
}
}
現在,我們可以使用 Redux combineReducers 實用程式組合這兩個 reducer。combineReducers 生成一個函式,該函式返回一個物件,其值是不同的 reducer 函式。您可以將所有 reducer 匯入 index reducer 檔案中,並將它們組合在一起作為具有各自名稱的物件。
/reducer/index.js
import { combineReducers } from ‘redux’;
import OrderStatusReducer from ‘./orderStatusReducer’;
import GetWishlistDataReducer from ‘./getWishlistDataReducer’;
const rootReducer = combineReducers ({
orderStatusReducer: OrderStatusReducer,
getWishlistDataReducer: GetWishlistDataReducer
});
export default rootReducer;
現在,您可以按如下方式將此 rootReducer 傳遞給 createStore 方法:
const store = createStore(rootReducer);
Redux - 中介軟體
Redux 本身是同步的,那麼像**非同步**操作(如**網路請求**)如何與 Redux 一起工作?這裡中介軟體派上用場了。如前所述,reducer 是編寫所有執行邏輯的地方。reducer 與誰執行它、花費多少時間或記錄 action 分派前後應用程式的狀態無關。
在這種情況下,Redux 中介軟體函式提供了一種在 action 達到 reducer 之前與已分派 action 互動的媒介。可以透過編寫高階函式(返回另一個函式的函式)來建立自定義中介軟體函式,該函式圍繞某些邏輯進行包裝。可以將多箇中間件組合在一起以新增新功能,並且每個中介軟體都不需要了解之前和之後的內容。您可以將中介軟體想象成 action 分派和 reducer 之間的某個地方。
通常,中介軟體用於處理應用程式中的非同步 action。Redux 提供了一個名為 applyMiddleware 的 API,它允許我們使用自定義中介軟體以及 Redux 中介軟體,如 redux-thunk 和 redux-promise。它將中介軟體應用於儲存。使用 applyMiddleware API 的語法如下:
applyMiddleware(...middleware)
這可以應用於儲存,如下所示:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(rootReducer, applyMiddleware(thunk));
中介軟體允許您編寫一個 action 分派器,該分派器返回一個函式而不是一個 action 物件。下面的示例顯示了相同的內容:
function getUser() {
return function() {
return axios.get('/get_user_details');
};
}
可以在中介軟體中編寫條件分派。每個中介軟體接收 store 的 dispatch,以便它們可以分派新的 action,並將 getState 函式作為引數,以便它們可以訪問當前狀態並返回一個函式。內部函式的任何返回值都將作為 dispatch 函式本身的值可用。
中介軟體的語法如下:
({ getState, dispatch }) => next => action
getState 函式可用於根據當前狀態確定是否要獲取新資料或返回快取結果。
讓我們看一個自定義中介軟體日誌記錄函式的示例。它只記錄 action 和新狀態。
import { createStore, applyMiddleware } from 'redux'
import userLogin from './reducers'
function logger({ getState }) {
return next => action => {
console.log(‘action’, action);
const returnVal = next(action);
console.log('state when action is dispatched', getState());
return returnVal;
}
}
現在透過編寫以下程式碼行將日誌記錄中介軟體應用於儲存:
const store = createStore(userLogin , initialState=[ ] , applyMiddleware(logger));
分派一個 action 來檢查使用以下程式碼分派的 action 和新狀態:
store.dispatch({
type: 'ITEMS_REQUEST',
isLoading: true
})
另一箇中間件示例,您可以在其中處理何時顯示或隱藏載入程式,如下所示。當您請求任何資源時,此中介軟體會顯示載入程式,並在資源請求完成後將其隱藏。
import isPromise from 'is-promise';
function loaderHandler({ dispatch }) {
return next => action => {
if (isPromise(action)) {
dispatch({ type: 'SHOW_LOADER' });
action
.then(() => dispatch({ type: 'HIDE_LOADER' }))
.catch(() => dispatch({ type: 'HIDE_LOADER' }));
}
return next(action);
};
}
const store = createStore(
userLogin , initialState = [ ] ,
applyMiddleware(loaderHandler)
);
Redux - 開發者工具
Redux-Devtools 為 Redux 應用程式提供除錯平臺。它允許我們執行時間旅行除錯和即時編輯。官方文件中的一些功能如下:
它允許您檢查每個狀態和 action 負載。
它允許您透過“取消” action 返回過去。
如果更改 reducer 程式碼,每個“分階段” action 都將重新評估。
如果 reducer 丟擲異常,我們可以識別錯誤以及發生此錯誤的 action。
使用 persistState() store enhancer,您可以跨頁面重新載入持久化除錯會話。
Redux dev-tools 有兩種變體,如下所示:
**Redux DevTools** - 它可以作為包安裝並整合到您的應用程式中,如下所示:
https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md#manual-integration
**Redux DevTools Extension** - 一個瀏覽器擴充套件,它實現相同的 Redux 開發工具,如下所示:
https://github.com/zalmoxisus/redux-devtools-extension
現在讓我們檢查如何跳過 action 並藉助 Redux dev 工具返回過去。以下螢幕截圖解釋了我們之前為獲取專案列表而分派的 action。在這裡,我們可以看到在檢查器選項卡中分派的 action。在右側,您可以看到演示選項卡,它向您顯示狀態樹的差異。
當您開始使用此工具時,您會熟悉它。您可以只從這個 Redux 外掛工具分派 action,而無需編寫實際程式碼。最後一行中的分派器選項將在此方面為您提供幫助。讓我們檢查專案成功獲取的最後一個 action。
我們從伺服器收到一個物件陣列作為響應。所有資料都可用於在我們的頁面上顯示列表。您還可以透過單擊右上角的狀態選項卡來同時跟蹤 store 的狀態。
在前面的章節中,我們學習了時間旅行除錯。現在讓我們檢查如何跳過一個 action 並返回過去以分析應用程式的狀態。當您單擊任何 action 型別時,將出現兩個選項:“跳轉”和“跳過”。
透過單擊特定 action 型別的跳過按鈕,您可以跳過特定 action。它就像 action 從未發生過一樣。當您單擊特定 action 型別的跳轉按鈕時,它將帶您到該 action 發生時的狀態,並跳過序列中所有剩餘的 action。透過這種方式,您將能夠保留特定 action 發生時的狀態。此功能有助於除錯和查詢應用程式中的錯誤。
我們跳過了最後一個 action,所有來自後臺的列表資料都消失了。它返回到專案資料尚未到達的時間,我們的應用程式沒有資料可在頁面上呈現。它實際上使編碼更容易,除錯也更容易。
Redux - 測試
測試 Redux 程式碼很容易,因為我們主要編寫函式,而且大多數函式都是純函式。因此,我們甚至無需模擬它們就可以對其進行測試。在這裡,我們使用 JEST 作為測試引擎。它在節點環境中工作,並且不訪問 DOM。
我們可以使用以下程式碼安裝 JEST:
npm install --save-dev jest
使用 babel,您需要安裝 **babel-jest**,如下所示:
npm install --save-dev babel-jest
並在 .babelrc 檔案中將其配置為使用 babel-preset-env 功能,如下所示:
{
"presets": ["@babel/preset-env"]
}
And add the following script in your package.json:
{
//Some other code
"scripts": {
//code
"test": "jest",
"test:watch": "npm test -- --watch"
},
//code
}
最後,**執行 npm test 或 npm run test**。讓我們檢查如何為 action 建立者和 reducer 編寫測試用例。
Action 建立者的測試用例
讓我們假設您有如下所示的 action 建立者:
export function itemsRequestSuccess(bool) {
return {
type: ITEMS_REQUEST_SUCCESS,
isLoading: bool,
}
}
此 action 建立者可以按如下所示進行測試:
import * as action from '../actions/actions';
import * as types from '../../constants/ActionTypes';
describe('actions', () => {
it('should create an action to check if item is loading', () => {
const isLoading = true,
const expectedAction = {
type: types.ITEMS_REQUEST_SUCCESS, isLoading
}
expect(actions.itemsRequestSuccess(isLoading)).toEqual(expectedAction)
})
})
Reducers 的測試用例
我們已經瞭解到,當應用 action 時,reducer 應該返回一個新的狀態。因此,reducer 在此行為上進行了測試。
考慮如下所示的 reducer:
const initialState = {
isLoading: false
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ITEMS_REQUEST':
return Object.assign({}, state, {
isLoading: action.payload.isLoading
})
default:
return state;
}
}
export default reducer;
要測試上面的 reducer,我們需要將狀態和 action 傳遞給 reducer,並返回一個新的狀態,如下所示:
import reducer from '../../reducer/reducer'
import * as types from '../../constants/ActionTypes'
describe('reducer initial state', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual([
{
isLoading: false,
}
])
})
it('should handle ITEMS_REQUEST', () => {
expect(
reducer(
{
isLoading: false,
},
{
type: types.ITEMS_REQUEST,
payload: { isLoading: true }
}
)
).toEqual({
isLoading: true
})
})
})
如果您不熟悉編寫測試用例,您可以檢視JEST的基礎知識。
Redux - 整合 React
在前面的章節中,我們學習了什麼是 Redux 以及它是如何工作的。現在讓我們檢查檢視部分與 Redux 的整合。您可以向 Redux 新增任何檢視層。我們還將討論 react 庫和 Redux。
比方說,如果各種 react 元件需要以不同的方式顯示相同的資料,而無需將其作為 prop 從頂級元件一直傳遞到各個元件。將其儲存在 react 元件之外是理想的選擇。因為它有助於更快地檢索資料,因為您無需將資料一直傳遞到不同的元件。
讓我們討論一下 Redux 如何實現這一點。Redux 提供 react-redux 包來繫結 react 元件,並提供如下所示的兩個實用程式:
- Provider
- Connect
Provider 使 store 可用於應用程式的其餘部分。Connect 函式幫助 react 元件連線到 store,響應 store 狀態中發生的每個更改。
讓我們看一下**根 index.js** 檔案,它建立 store 並使用一個 provider 來使 store 可用於 react-redux 應用程式中的其餘應用程式。
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers/reducer'
import thunk from 'redux-thunk';
import App from './components/app'
import './index.css';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
applyMiddleware(thunk)
)
render(
<Provider store = {store}>
<App />
</Provider>,
document.getElementById('root')
)
每當 react-redux 應用程式中發生更改時,都會呼叫 mapStateToProps()。在這個函式中,我們精確地指定我們需要提供給 react 元件的狀態。
藉助下面解釋的 connect() 函式,我們將這些應用程式的狀態連線到 react 元件。Connect() 是一個高階函式,它將元件作為引數。它執行某些操作並返回一個具有正確資料的新元件,我們最終將其匯出。
藉助 mapStateToProps(),我們將這些 store 狀態作為 prop 提供給我們的 react 元件。此程式碼可以包裝在一個容器元件中。其目的是分離關注點,例如資料獲取、呈現關注點和可重用性。
import { connect } from 'react-redux'
import Listing from '../components/listing/Listing' //react component
import makeApiCall from '../services/services' //component to make api call
const mapStateToProps = (state) => {
return {
items: state.items,
isLoading: state.isLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch(makeApiCall())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Listing);
在 services.js 檔案中進行 api 呼叫的元件定義如下:
import axios from 'axios'
import { itemsLoading, itemsFetchDataSuccess } from '../actions/actions'
export default function makeApiCall() {
return (dispatch) => {
dispatch(itemsLoading(true));
axios.get('http://api.tvmaze.com/shows')
.then((response) => {
if (response.status !== 200) {
throw Error(response.statusText);
}
dispatch(itemsLoading(false));
return response;
})
.then((response) => dispatch(itemsFetchDataSuccess(response.data)))
};
}
mapDispatchToProps() 函式接收 dispatch 函式作為引數,並返回您傳遞給 react 元件的普通物件作為回撥 prop。
在這裡,你可以訪問`fetchData`作為你React列表元件中的一個prop,它會分發一個action來進行API呼叫。`mapDispatchToProps()`用於向store分發action。在react-redux中,元件不能直接訪問store。唯一的方法是使用`connect()`。
讓我們透過下面的圖表來了解react-redux是如何工作的:
STORE − 將所有應用程式狀態儲存為一個JavaScript物件
PROVIDER − 使store可用
CONTAINER − 獲取應用狀態並將其作為prop提供給元件
COMPONENT − 使用者透過檢視元件進行互動
ACTIONS − 導致store發生變化,它可能改變也可能不改變應用程式的狀態
REDUCER − 唯一改變應用程式狀態的方法,接收狀態和action,並返回更新後的狀態。
然而,Redux是一個獨立的庫,可以與任何UI層一起使用。React-redux是Redux官方的React UI繫結。此外,它鼓勵良好的React Redux應用程式結構。React-redux內部實現了效能最佳化,以便只有在需要時才重新渲染元件。
總而言之,Redux的設計目的並非編寫最短、最快的程式碼。它旨在提供一個可預測的狀態管理容器。它幫助我們理解某個狀態何時發生變化,或者資料來自哪裡。
Redux - React 示例
這是一個React和Redux應用程式的小例子。你也可以嘗試開發小型應用程式。下面給出了增加或減少計數器的示例程式碼:
這是根檔案,負責建立store和渲染我們的React應用程式元件。
/src/index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux';
import reducer from '../src/reducer/index'
import App from '../src/App'
import './index.css';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
)
render(
<Provider store = {store}>
<App />
</Provider>, document.getElementById('root')
)
這是我們的React根元件。它負責渲染計數器容器元件作為子元件。
/src/app.js
import React, { Component } from 'react';
import './App.css';
import Counter from '../src/container/appContainer';
class App extends Component {
render() {
return (
<div className = "App">
<header className = "App-header">
<Counter/>
</header>
</div>
);
}
}
export default App;
以下是容器元件,負責向React元件提供Redux的狀態:
/container/counterContainer.js
import { connect } from 'react-redux'
import Counter from '../component/counter'
import { increment, decrement, reset } from '../actions';
const mapStateToProps = (state) => {
return {
counter: state
};
};
const mapDispatchToProps = (dispatch) => {
return {
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement()),
reset: () => dispatch(reset())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
下面是負責檢視部分的React元件:
/component/counter.js
import React, { Component } from 'react';
class Counter extends Component {
render() {
const {counter,increment,decrement,reset} = this.props;
return (
<div className = "App">
<div>{counter}</div>
<div>
<button onClick = {increment}>INCREMENT BY 1</button>
</div>
<div>
<button onClick = {decrement}>DECREMENT BY 1</button>
</div>
<button onClick = {reset}>RESET</button>
</div>
);
}
}
export default Counter;
以下是負責建立action的action creators:
/actions/index.js
export function increment() {
return {
type: 'INCREMENT'
}
}
export function decrement() {
return {
type: 'DECREMENT'
}
}
export function reset() {
return { type: 'RESET' }
}
下面,我們展示了reducer檔案的程式碼片段,它負責更新Redux中的狀態。
reducer/index.js
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT': return state + 1
case 'DECREMENT': return state - 1
case 'RESET' : return 0 default: return state
}
}
export default reducer;
最初,應用程式如下所示:
當我點選兩次“遞增”按鈕時,輸出螢幕將如下所示:
當我們點選一次“遞減”按鈕時,它將顯示以下螢幕:
而“重置”將使應用程式返回初始狀態,即計數器值為0。如下所示:
讓我們瞭解一下第一次遞增操作發生時Redux開發者工具會發生什麼:
應用程式的狀態將移動到僅分發遞增操作且其餘操作被跳過的時間點。
我們鼓勵你獨立開發一個小的Todo應用程式作為練習,以便更好地理解Redux工具。