Redux 狀態(tài)容器、管理
當你越來越有能力時,自然會有人看得起你;改變自己,你才有自信,夢想才會慢慢的實現(xiàn)。噴泉之所以漂亮是因為她有了壓力;瀑布之所以壯觀是因為她沒有了退路;水之所以能穿石是因為永遠在堅持。
首先我們要明確一個 React 組件,它與數(shù)據(jù)掛鉤的只有 props 和 state,一個是從上級傳下來的數(shù)據(jù),一個是內部的狀態(tài),只能向下傳,不能直接向上傳。
這樣的話,我們如何處理同級別組件的通信呢?一個最直接的方式就是創(chuàng)建一個最頂層的 state,把數(shù)據(jù)當作 props 向下傳。這個最頂層的存放 state 的地方,我們可以認為是 store。
1. Flux 和 Redux
Flux 是一種架構思想,專門解決軟件的結構問題,它跟 MVC 架構是同一類東西,但是更加簡單和清晰。Flux 存在至少多種實現(xiàn),比如:Redux。
Facebook Flux 是用來構建客戶端 Web 應用的應用架構。利用單向數(shù)據(jù)流的方式來組合 React 中的視圖組件。它更像一個模式而不是一個正式的框架,開發(fā)者不需要太多的新代碼就可以快速的上手 Flux。
中心思想流程:
- 用戶訪問 View,View 訂閱 Store;
- View 發(fā)出用戶的 Action;
- Dispatcher 收到 Action,要求 Store 進行相應的更新;
- Store 更新后,發(fā)出一個 Change 事件;
- View 收到 Change 事件后,更新頁面;

Redux 最主要是用作應用狀態(tài)的管理。簡言之,Redux 用一個單獨的常量狀態(tài)樹(state對象),保存這一整個應用的狀態(tài),這個對象不能直接被改變,當一些數(shù)據(jù)變化了,一個新的對象就會被創(chuàng)建(使用 actions 和 reducers)這樣就可以進行數(shù)據(jù)追蹤,實現(xiàn)時光旅行。
2. Redux 工作流
Redux 原理就是訂閱、發(fā)布模式。我們看其中主要的幾個重要的概念:
- 組件 Component
- Actions:用戶系統(tǒng)行為,Actions Creators 創(chuàng)建;
- Store:state 以單一對象存儲在 store 中:
- 狀態(tài)調度:Store.dispatch(action);
- 狀態(tài)展示:Store.getState();
- 訂閱改變: Store.subscribe();
- Reducers:純處理函數(shù) reducer,里面對老狀態(tài)處理,得到新狀態(tài);

點擊組件按鈕觸發(fā)一個事件,通過 Action Creator 創(chuàng)建一個 action 對象。通過 dispatch 把這個 action 對象發(fā)送到 store 里面。在 store 里面 需要通過 reducers 來更新狀態(tài)。store 自己是無法更新狀態(tài)的。reducers 必須接收老的對象和 action,然后根據(jù) action 的 type 不同進行處理,返回新的狀態(tài),新的狀態(tài)更新了,store 就會通知那些訂閱者組件進行更新。
Redux store 默認將數(shù)據(jù)存儲在內存中,因此?頁面刷新后數(shù)據(jù)會丟失?。這是因為瀏覽器刷新會重新加載整個應用,內存中的狀態(tài)會被重置?。
Action 文件:
var increment = () => {
return {
type: "increment"
}
}
var decrement = () => {
return {
type: "decrement"
}
}
export { increment, decrement }
Store 文件:
import { createStore } from "@reduxjs/toolkit";
import counterReducer from "./reducer";
const Store = createStore(counterReducer)
export default Store
Reducer 文件:
const counterReducer = (state, action) => {
if (state === undefined) {
return 0
}
switch (action.type) {
case "increment": {
let newState = state
newState += 1
return newState
}
case "decrement":{
let newState = state
newState -= 1
return newState
}
default:
return state
}
}
export default counterReducer
應用示例:
import React, { useEffect, useState } from 'react'
import Store from './1-AppStore/store'
import { increment, decrement } from './1-AppStore/actions'
export default function ReduxBaseJs() {
const [count, setcount] = useState(Store.getState())
useEffect(()=>{
Store.subscribe(() => {
console.log(Store.getState())
setcount(Store.getState())
})
})
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => Store.dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => Store.dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
)
}
3. Redux 使用三大原則
- state 以單一對象存儲在 store 對象中;
- state 只讀,每次更新都是返回一個新的對象;
- 使用純函數(shù) reducer 執(zhí)行 state 更新;
- 對外界沒有副作用。即調用后對外界變量、對象沒有影響。
- 同樣的輸入得到同樣的輸出。
4. 你需要使用 Redux 嗎?
雖然 Redux 是一個很有價值的管理狀態(tài)工具,但還是要考慮下它是否適合你的場景。不要僅僅因為有人說過應該使用 Redux 而使用 - 應該花一些時間來了解使用它的潛在好處和取舍。當遇到如下問題時,建議開始使用 Redux:
- 你有很多數(shù)據(jù)隨時間而變化;
- 你希望狀態(tài)有一個唯一確定的來源(single source of truth);
- 你發(fā)現(xiàn)將所有狀態(tài)放在頂層組件中管理已不可維護;
5. Redux 使用之 Reducer 函數(shù)拆分 combineReducers
應用的整體全局狀態(tài)以對象樹的方式存放于單個 store。 唯一改變狀態(tài)樹(state tree)的方法是創(chuàng)建 action,一個描述發(fā)生了什么的對象,并將其 dispatch 給 store。 要指定狀態(tài)樹如何響應 action 來進行更新,你可以編寫純 reducer 函數(shù),這些函數(shù)根據(jù)舊 state 和 action 來計算新 state。
Redux 擴展(行為拆分),如果不同的 Action 所處理的屬性之間沒有聯(lián)系,我們可以把 Reducer 函數(shù)拆分。不同的函數(shù)負責處理不同屬性,最終把它們合并成一個大的 Reducer 即可。那在 dispatch(action) 的時候是怎么知道用那個 reducer 來處理的呢?所有的 reducer 都會執(zhí)行一遍,其實就是所有的 reducer 輪詢匹配。
store.js 文件:
// 創(chuàng)建合并 Reducer
import {combineReducers} from "redux";
const reducer = combineReducers ({
aReducer,
bReducer,
cReducer
})
const store = createStore(reducer)
訪問:
// 訪問:
store.getState.aReducer.property // 不同的命名空間
TS 示例:
reducer.ts 文件:
// 定義一個 Reducer 純函數(shù)
const counterReducer = (state: any, action: any) => {
if (state === undefined) {
return 0
}
switch (action.type) {
case "increment": {
let newState = state
newState += 1
return newState
}
case "decrement":{
let newState = state
newState -= 1
return newState
}
default:
return state
}
}
export default counterReducer
actions.ts 文件:
// 定義相關行為 action
var increment = () => {
return {
type: "increment"
}
}
var decrement = () => {
return {
type: "decrement"
}
}
export { increment, decrement }
store.ts 文件:
import { createStore } from "@reduxjs/toolkit";
import counterReducer from "./reducer";
const Store = createStore(counterReducer)
export default Store
component.ts 文件:
import React, { useEffect, useState } from 'react'
import Store from './2-AppStore/store'
import { increment, decrement } from './2-AppStore/actions'
export default function ReduxBaseTs() {
const [count, setcount] = useState(Store.getState())
useEffect(() => {
Store.subscribe(() => {
console.log(Store.getState())
setcount(Store.getState())
})
})
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => Store.dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => Store.dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
)
}
??:以上基礎使用方法已經(jīng)不是官方推薦的編寫方式了,現(xiàn)在推薦使用 Redux Toolkit 編寫 Redux 邏輯的方法。
6. React-redux
其實 Redux 和 React 沒有任何關系,它是基于 Flux 實現(xiàn)的一套可用于 React 狀態(tài)管理的庫。而 React-redux 是基于 Redux 庫(必須引入、依賴 Redux),在 Redux 基礎上多了一點 React 特性。幫你構建父組件以及訂閱和發(fā)布這樣的一些事情。這樣在 React 中狀態(tài)管理使用更加方便。
React-redux 在 Redux 基礎上,通過 connect 高階函數(shù)生成高階組件(父組件)包裝訂閱、發(fā)布功能(幫你訂閱和取消訂閱),不用開發(fā)者自己發(fā)起訂閱和發(fā)布。其次通過最外層 Provider 供應商組件負責把 store 跨級給 connect 組件,原理就是通過 context 一級一級將 store 傳遞給 connect 組件。即通過 connect 包裝就將 App UI 組件變成了容器組件,之前的組件就變成了 UI 組件。具體應用參考 Redux 中文官網(wǎng)。
Redux Toolkit 一般在 React 項目中結合 React-redux 使用。
》UI 組件與容器組件:
1)UI 組件
- 只負責 UI 的呈現(xiàn),不帶有任何業(yè)務邏輯;
- 沒有狀態(tài)(即不使用 this.state 這個變量),所有數(shù)據(jù)都由參數(shù)(this.props)提供;
- 不使用任何 Redux 的 API;
2)容器組件
- 負責管理數(shù)據(jù)和業(yè)務邏輯,不負責 UI 的呈現(xiàn);
- 帶有內部狀態(tài);
- 使用 Redux 的 API;
》高階組件(HOC:Higher order components) 與 context 通信在 react-redux 底層中的應用:
- connect 是 HOC,高階組件;
- Provider 組件,可以讓容器組件拿到 state,使用了context;
高階組件構建與應用:HOC 不僅僅是一個方法,確切說應該是一個組件工廠,獲取低階組件,生成高階組件。
- 代碼復用,代碼模塊化;
- 增刪改 props;
- 渲染劫持;
7. Redux ToolKit
Redux Toolkit 簡化了編寫 Redux 邏輯和設置 store 的過程。 使用 Redux Toolkit,相同的示例邏輯如下所示。更詳細使用可以參考 Demo 工程和 Redux 中文官網(wǎng)。
相關 API:
- createSlice;
- configureStore;
TS 示例:
import { createSlice, configureStore } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
incremented: state => {
// Redux Toolkit 允許在 reducers 中編寫 "mutating" 邏輯。
// 它實際上并沒有改變 state,因為使用的是 Immer 庫,檢測到“草稿 state”的變化并產(chǎn)生一個全新的
// 基于這些更改的不可變的 state。
state.value += 1
},
decremented: state => {
state.value -= 1
}
}
})
export const { incremented, decremented } = counterSlice.actions
const store = configureStore({
reducer: counterSlice.reducer
})
// 可以訂閱 store
store.subscribe(() => console.log(store.getState()))
// 將我們所創(chuàng)建的 action 對象傳遞給 `dispatch`
store.dispatch(incremented())
// {value: 1}
store.dispatch(incremented())
// {value: 2}
store.dispatch(decremented())
// {value: 1}
8. Redux 中間件
在 redux 中,action 僅僅是攜帶了數(shù)據(jù)的普通 js 對象。action creator 返回的值是這個 action 類型的對象。然后通過 store.dispatch() 進行分發(fā)。同步情況下一切都很完美,但是 reducer 無法處理異步的情況。那么我們就需要在 action 和 reducer 中間架起一座橋梁來處理異步。這就是 middleware。
中間件的由來與原理、機制:
export default function thunkMiddleware({ dispatch, getstate }) {
return next => action =>
typeof action === 'function'?
action (dispatch, getstate):
next (action);
}
這段代碼的意思是,中間件這個橋梁接受到的參數(shù) action,如果不是 function 則和過去一樣直接執(zhí)行 next 方法(下一步處理),相當于中間件沒有做任何事。如果 action 是 function,則先執(zhí)行 action,action 的處理結束之后,再在 action 的內部調用 dispatch。
8.1 redux-thunk
到目前為止,我們學習到所有邏輯都是同步的。我們需要一個地方在我們的 Redux 應用程序中放置異步邏輯。這就需要使用中間件 redux-thunk。
thunk 是一種特定類型的 Redux 函數(shù),可以包含異步邏輯。Thunk 是使用兩個函數(shù)編寫的:
- 一個內部 thunk 函數(shù),它以
dispatch和getState作為參數(shù); - 外部創(chuàng)建者函數(shù),它創(chuàng)建并返回 thunk 函數(shù);
從 counterSlice 導出的函數(shù)就是一個 thunk action creator 的例子。
// 下面這個函數(shù)就是一個 thunk ,它使我們可以執(zhí)行異步邏輯
// 你可以 dispatched 異步 action `dispatch(incrementAsync(10))` 就像一個常規(guī)的 action
// 調用 thunk 時接受 `dispatch` 函數(shù)作為第一個參數(shù)
// 當異步代碼執(zhí)行完畢時,可以 dispatched actions
export const incrementAsync = amount => dispatch => {
setTimeout(() => {
dispatch(incrementByAmount(amount))
}, 1000)
}
我們可以像使用普通 Redux action creator 一樣使用它們:
store.dispatch(incrementAsync(5))
8.2 redux-promise
Redux 異步邏輯另外一種解決方案 redux-promise 中間件。用 promise 對象代替 Redux-thunk 中的函數(shù)。
8.3 redux-saga
redux-saga 相比 redux-thunk、redux-promise 能夠非侵入式結合 redux 進行開發(fā)。讓你的 action 還是之前那個普通的 action 對象,然后你需要引入我 saga 中的一些任務、effect 作用等等來處理。
8.3.1 生成器函數(shù) Generator
Generator 生成器函數(shù),ES6 中提供異步編程的一種解決方案。有時候也被被人稱為狀態(tài)機,可以讓函數(shù)中斷執(zhí)行,等你需要推一步就走一步,可以生成輸出多個狀態(tài),所以又叫狀態(tài)機。
生成器函數(shù)特征:
- 函數(shù)名前面增加 * 號。
- 必須使用關鍵字 yield(產(chǎn)出狀態(tài)值)。
- yield 后面跟的是狀態(tài)機生成的狀態(tài)。即當遇到 yield 表達式,就暫停執(zhí)行后面的操作,并將緊跟在yield后面的那個表達式的值,作為返回的對象的 value 屬性值。yield 表達式本身沒有返回值,或者說總是返回undefined。
- next 方法可以帶一個參數(shù),該參數(shù)就會被當作上一個 yield 表達式的返回值。
function *test() {
console.log("111111")
yield;
console.log("222222")
yield;
console.log("333333")
yield;
}
let generator = test()
// next() 執(zhí)行器函數(shù)執(zhí)行一次,直到遇到 yield 關鍵字
generator.next() // 111111
generator.next() // 222222
generator.next() // 333333
generator.next() // 沒有任何輸出了,已經(jīng)結束了
function *test1() {
console.log("111111")
let value1 = yield "yield return 1 step";
console.log("222222", value1)
let value2 = yield "yield return 2 step";
console.log("333333", value2)
let value3 = yield "yield return 3 step";
console.log("333333", value3)
}
let generator1 = test1()
let gen1 = generator1.next("1 next 參數(shù)")
console.log(gen1) // {value: 'yield return 1 step', done: false}
let gen2 = generator1.next("2 next 參數(shù)")
console.log(gen2) // {value: 'yield return 2 step', done: false}
let gen3 =generator1.next("3 next 參數(shù)")
console.log(gen3) // {value: 'yield return 3 step', done: false}
let gen4 =generator1.next("4 next 參數(shù)")
console.log(gen4) // {value: undefined, done: true}
異步鏈式調用更簡單的寫法 async-await 寫法,async-await 本質是生成器的一套語法糖,內置了執(zhí)行器函數(shù)。讓異步變寫得和同步的一樣簡單。但是這里 redux-saga 是基于生成器函數(shù)來實現(xiàn)的,我們了解即可:
async function test() {
var res1 = await fetch();
var res2 = await fetch(res1);
var res3 = await fetch(res2);
}
8.3.2 redux-saga 應用
在 saga 中,全局監(jiān)聽器和接收器使用 Generator 函數(shù)和 saga 自身的一些輔助函數(shù)實現(xiàn)對整個流程的管控。

// Component 組件內部
dispatch({action:"get-list"})
// WatcherSaga
// saga.js 文件
function *watchSaga() {
while(true) {
// take 監(jiān)聽 組件發(fā)來的 action
yield take("get-list")
// fork 同步非阻塞執(zhí)行函數(shù) getList
yield fork(getList)
}
}
function *getList() {
// 異步處理:call 函數(shù)發(fā)布異步請求 - 阻塞式調用
let res = yield call(getListAction) //這里傳入返回值是promise對象的函數(shù)
// put 函數(shù)發(fā)出新的 action
yield put({
type: "change-list",
payload: res
})
}
function getListAction() {
return new Promise((resolve, reject)=>{
setTime(()={
resolve(["111","222","333"])
},2000)
})
}
export default watchSage
// store.js 文件
import {createStore, applyMiddleware} from 'redux'
import reducer from ' /reducer'
import createSagaMidlleWare from 'redux-saga'
import watchSaga from ?/ saga'
const SagaMidlleWare = createSagaMidlleWare()
const store = createStore(reducer, applyMiddleware(SagaMidlleWare))
SagaMidlleWare.run(latchSaga) //saga 任務,
export default store
多任務同時監(jiān)聽 all:
// WatcherSaga
// saga2.js 文件
export default watchSage
// 聚合統(tǒng)一監(jiān)聽多個任務 saga.js 文件
import {all} from 'redux-saga/effects'
import watchSagal from '?/saga/sagal'
import watchSaga2 from './saga/saga2'
function *watchSaga(){
yield all([watchSaga1(),watchSaga2()])
}
export default watchSaga
多異步鏈式流程調用:
function *getList() {
// 異步處理:call 函數(shù)發(fā)布異步請求 - 阻塞式調用
let res = yield call(getListAction) //這里傳入返回值是promise對象的函數(shù)
let res1 = yield call(getListAction1, res)
// put 函數(shù)發(fā)出新的 action
yield put({
type: "change-list",
payload: res1
})
}
function getListAction() {
return new Promise((resolve, reject)=>{
setTime(()={
resolve(["111","222","333"])
},2000)
})
}
function getListAction1(data) {
return new Promise((resolve, reject)=>{
setTime(()={
resolve([...data, "444"])
},2000)
})
}
export default watchSage
watchSaga 函數(shù)新寫法-合并 take 和 fork:
// WatcherSaga
// saga.js 文件
function *watchSaga() {
/*while(true) {
// take 監(jiān)聽 組件發(fā)來的 action
yield take("get-list")
// fork 同步非阻塞執(zhí)行函數(shù) getList
yield fork(getList)
}*/
yield takeEvery("get-list", getList)
}
8.3.3 redux-saga 應用場景
在 React-Redux 應用中,?redux-saga 主要用于管理復雜異步邏輯和副作用?,尤其在以下場景中具有顯著優(yōu)勢:
?多步驟異步操作:?當操作涉及多個順序/并行的異步任務(如:登錄 → 獲取用戶信息 → 加載權限列表),saga 的 Generator 函數(shù)可用 yield 精確控制每一步流程,避免回調地獄?
function* loginFlow() {
yield call(loginAPI); // 步驟1:登錄
yield call(fetchUserInfo); // 步驟2:獲取用戶信息
yield call(loadPermissions); // 步驟3:加載權限
}
?依賴異步結果的后續(xù)操作:若后續(xù)操作需依賴多個異步任務結果(如:支付需同時驗證賬戶余額和風控狀態(tài)),saga 可通過 all 實現(xiàn)并行請求,并統(tǒng)一處理結果?。
優(yōu)選 saga 的場景:?多步驟異步?、?高可測性要求?、?長時運行任務?(如實時通信)?。
簡單場景(單一請求)可使用 redux-thunk 或 Redux Toolkit 內置方案?。
9. Redux 插件
9.1 redux-persist
redux-persist 是一個用于 Redux 狀態(tài)管理的持久化插件,允許將應用狀態(tài)保存到本地存儲(如 localStorage),以便在應用重啟或頁面刷新時恢復狀態(tài)。必須配合 React-redux 使用。

浙公網(wǎng)安備 33010602011771號