安裝 Redux Toolkit 和 React-Redux
添加 Redux Toolkit 和 React-Redux 依賴包到你的項目中:
npm install @reduxjs/toolkit react-redux
創(chuàng)建 Redux Store
創(chuàng)建 src/store/index.js 文件。從 Redux Toolkit 引入 configureStore API。我們從創(chuàng)建一個空的 Redux store 開始,并且導(dǎo)出它:
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: {}
})
為 React 注入 Redux Store
創(chuàng)建 store 后,便可以在 React 組件中使用它。 在 src/index.js 中引入我們剛剛創(chuàng)建的 store , 通過 React-Redux 的 <Provider>將 <App> 包裹起來,并將 store 作為 prop 傳入。
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import store from './store/index.js'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
創(chuàng)建 Redux State Slice
創(chuàng)建 src/store/modules/counterStore.js 文件。在該文件中從 Redux Toolkit 引入 createSlice API。
創(chuàng)建 slice 需要一個字符串名稱來標(biāo)識切片、一個初始 state 以及一個或多個定義了該如何更新 state 的 reducer 函數(shù)。slice 創(chuàng)建后 ,我們可以導(dǎo)出 slice 中生成的 Redux action creators 和 reducer 函數(shù)。
// src/store/modules/counterStore.js
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {
// Redux Toolkit 允許我們在 reducers 寫 "可變" 邏輯。它
// 并不是真正的改變狀態(tài)值,因為它使用了 Immer 庫
// 可以檢測到“草稿狀態(tài)“ 的變化并且基于這些變化生產(chǎn)全新的
// 不可變的狀態(tài)
state.value += 1
},
decrement: state => {
state.value -= 1
},
// 第二個參數(shù)action的payload屬性接收來自組件中dispatch action的傳參
incrementByAmount: (state, action) => {
state.value += action.payload
}
}
})
// 每個 case reducer 函數(shù)會生成對應(yīng)的 Action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
將 Slice Reducers 添加到 Store 中
下一步,我們需要從計數(shù)切片中引入 reducer 函數(shù),并將它添加到我們的 store 中。通過在 reducer 參數(shù)中定義一個字段,我們告訴 store 使用這個 slice reducer 函數(shù)來處理對該狀態(tài)的所有更新。
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'
export default configureStore({
reducer: {
counter: counterReducer
}
})
在 React 組件中使用 Redux 狀態(tài)和操作
現(xiàn)在我們可以使用 React-Redux 鉤子讓 React 組件與 Redux store 交互。我們可以使用 useSelector 從 store 中讀取數(shù)據(jù),使用 useDispatch dispatch actions。
import { useSelector, useDispatch } from 'react-redux';
import {
decrement,
increment,
incrementByAmount,
} from './store/modules/counterStore.js';
export default function ReduxCounter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(decrement())}>-</button>
{count}
<button onClick={() => dispatch(increment())}>+</button>
{/* dispatch action傳遞的參數(shù)會在reducer函數(shù)的第二個參數(shù)action.payload進(jìn)行接收 */}
<button onClick={() => dispatch(incrementByAmount(10))}>add 10</button>
</div>
);
}
用 Thunk 編寫異步邏輯
到目前為止,我們應(yīng)用程序中的所有邏輯都是同步的。首先 dispatch action,store 調(diào)用 reducer 來計算新狀態(tài),然后 dispatch 函數(shù)完成并結(jié)束。但是,JavaScript 語言有很多編寫異步代碼的方法,我們的應(yīng)用程序通常具有異步邏輯,比如從 API 請求數(shù)據(jù)之類的事情。我們需要一個地方在我們的 Redux 應(yīng)用程序中放置異步邏輯。
thunk 是一種特定類型的 Redux 函數(shù),可以包含異步邏輯。Thunk 是使用兩個函數(shù)編寫的:
- 一個內(nèi)部 thunk 函數(shù),它以 dispatch 和 getState 作為參數(shù)
- 外部創(chuàng)建者函數(shù),它創(chuàng)建并返回 thunk 函數(shù)
比如我們從 counterStore 導(dǎo)出一個函數(shù)incrementAsync,incrementAsync就是一個 thunk action creator。
// 下面這個函數(shù)就是一個 thunk ,它使我們可以執(zhí)行異步邏輯
// 你可以 dispatched 異步 action `dispatch(incrementAsync(10))` 就像一個常規(guī)的 action
// 調(diào)用 thunk 時接受 `dispatch` 函數(shù)作為第一個參數(shù)
// 當(dāng)異步代碼執(zhí)行完畢時,可以 dispatched actions
export const incrementAsync = amount => dispatch => {
setTimeout(() => {
dispatch(incrementByAmount(amount))
}, 1000)
}
我們可以像使用普通 Redux action creator 一樣使用它們:
store.dispatch(incrementAsync(5))
但是,使用 thunk 需要在創(chuàng)建時將 redux-thunk middleware(一種 Redux 插件)添加到 Redux store 中。幸運的是,Redux Toolkit 的 configureStore 函數(shù)已經(jīng)自動為我們配置好了,所以我們可以繼續(xù)在這里使用 thunk。
當(dāng)你需要進(jìn)行 AJAX 調(diào)用以從服務(wù)器獲取數(shù)據(jù)時,你可以將該調(diào)用放入 thunk 中。這是一個寫得有點長的例子,所以你可以看到它是如何定義的:
// 外部的 thunk creator 函數(shù)
const fetchUserById = userId => {
// 內(nèi)部的 thunk 函數(shù)
return async (dispatch, getState) => {
try {
// thunk 內(nèi)發(fā)起異步數(shù)據(jù)請求
const user = await userAPI.fetchById(userId)
// 但數(shù)據(jù)響應(yīng)完成后 dispatch 一個 action
dispatch(userLoaded(user))
} catch (err) {
// 如果過程出錯,在這里處理
}
}
}
dispatch thunk action creator
useEffect(() => {
dispatch(fetchUserById(5));
}, [dispatch]);
總結(jié)
我們可以使用 Redux Toolkit configureStore API 創(chuàng)建一個 Redux store
- configureStore 接收 reducer 函數(shù)來作為命名參數(shù)
- configureStore 自動使用默認(rèn)值來配置 store
在 slice 文件中編寫 Redux 邏輯
- 一個 slice 包含一個特定功能或部分的 state 相關(guān)的 reducer 邏輯和 action
- Redux Toolkit 的 createSlice API 為你提供的每個 reducer 函數(shù)生成 action creator 和 action 類型
Redux reducer 必須遵循以下原則
- 必須依賴 state 和 action 參數(shù)去計算出一個新 state
- 必須通過拷貝舊 state 的方式去做 不可變更新 (immutable updates)
- 不能包含任何異步邏輯或其他副作用
- Redux Toolkit 的 createSlice API 內(nèi)部使用了 Immer 庫才達(dá)到表面上直接修改("mutating")state 也實現(xiàn)不可變更新(immutable updates)的效果
一般使用 “thunks” 來開發(fā)特定的異步邏輯
- Thunks 接收 dispatch 和 getState 作為參數(shù)
- Redux Toolkit 內(nèi)置并默認(rèn)啟用了 redux-thunk 中間件
使用 React-Redux 來做 React 組件和 Redux store 的通信
- 在應(yīng)用程序根組件包裹
<Provider store={store}>使得所有組件都能訪問到 store - 全局狀態(tài)應(yīng)該維護(hù)在 Redux store 內(nèi),局部狀態(tài)應(yīng)該維護(hù)在局部 React 組件內(nèi)