新聞中心
最近在某項(xiàng)目中欲選一工具用作項(xiàng)目的全局狀態(tài)管理,通過(guò)綜合比較考慮,最終選擇了 Redux。都說(shuō) Redux 難上手,今天通過(guò) 1 個(gè)案例, 3 個(gè)函數(shù)幫小伙伴們快速掌握并實(shí)踐生產(chǎn)!

作為一名前端工程師,不少小伙伴對(duì)于 Redux 的概念、使用可能還是比較模糊,上手使用的心智負(fù)擔(dān)也比較重!
但通過(guò)調(diào)研,目前 Redux 的生態(tài)可以說(shuō)是非常豐富,這也使得將其引入作為項(xiàng)目的狀態(tài)管理工具庫(kù)變得 更加容易。
本文通過(guò)實(shí)際案例反向釋義 Redux 中的名詞概念,同時(shí)借助 @reduxjs/toolkit 模塊簡(jiǎn)化 Redux 的使用,希望通過(guò)今天的分享可以幫助大家打開(kāi)心結(jié),抱抱 Redux,提升工作效率,從此不加班!
一、Redux 基礎(chǔ)
一開(kāi)始就闡釋概念名詞,可能會(huì)增加大家上手的難度,因此該部分只對(duì) Redux 做最基本的一個(gè)認(rèn)識(shí)。
1、 什么是 Redux ?
Redux 是 JavaScript 狀態(tài)容器,提供 可預(yù)測(cè)、可調(diào)試、集中式 的狀態(tài)管理。
2、特點(diǎn)
- 可預(yù)測(cè): 讓你開(kāi)發(fā)出 行為穩(wěn)定可預(yù)測(cè)、可運(yùn)行在不同環(huán)境 (客戶(hù)端、服務(wù)端和原生程序)、且 易于測(cè)試 的應(yīng)用。
- 集中管理: 集中管理應(yīng)用的狀態(tài)和邏輯可以讓你開(kāi)發(fā)出強(qiáng)大的功能,如 撤銷(xiāo)/重做、 狀態(tài)持久化 等等。
- 可調(diào)試: Redux DevTools 讓你 輕松追蹤 到 應(yīng)用的狀態(tài)在何時(shí)、何處以及如何改變。Redux 的架構(gòu)會(huì)記下每一次改變,借助于 "時(shí)間旅行調(diào)試",你甚至可以把完整的錯(cuò)誤報(bào)告發(fā)送給服務(wù)器。
- 靈活: Redux 可與任何 UI 層框架搭配使用,它體小精干(只有 2kB,包括依賴(lài)),并且有 龐大的插件生態(tài) 來(lái)實(shí)現(xiàn)你的需求。
3、 設(shè)計(jì)思想
Redux 既然是狀態(tài)管理庫(kù),那么接下來(lái)掌握一下基本的數(shù)據(jù)流概念和原則。
(1) 單一數(shù)據(jù)源
整個(gè)應(yīng)用的 全局 state 被儲(chǔ)存在一棵對(duì)象樹(shù)(object tree)中,并且這個(gè)對(duì)象樹(shù)只存在于唯一 Store(存儲(chǔ)) 中。
單一數(shù)據(jù)源使得同構(gòu)應(yīng)用開(kāi)發(fā)變得容易,將狀態(tài)在統(tǒng)一的 對(duì)象樹(shù) 中維護(hù)管理也會(huì)更加容易!
(2) 單向數(shù)據(jù)流(one-way data flow)
Redux 單向數(shù)據(jù)流
- 用 state 來(lái)描述應(yīng)用程序在特定時(shí)間點(diǎn)的狀況。
- 基于 state 來(lái)渲染出 View。
- 當(dāng)發(fā)生某些事情時(shí)(例如用戶(hù)單擊按鈕),state 會(huì)根據(jù)發(fā)生的事情進(jìn)行更新,生成新的 state。
- 基于新的 state 重新渲染 View。
(3) 不可變性(Immutability)
對(duì)于狀態(tài)(state)的描述一般都是一個(gè)大的 JavaScript 對(duì)象(Object Tree),例如:
const state = {
isLoading: true,
userInfo: {
uid: 1,
wechat: 'DYBOY2020',
phone: 177****7777,
history: [1,2,3,4,5]
}
}
由于 JS 的動(dòng)態(tài)性,使得對(duì)象是可以修改的,Redux 想要記錄每一個(gè)狀態(tài),如果直接修改 state 中的引用類(lèi)型屬性,勢(shì)必會(huì)導(dǎo)致 state 的變化不可追溯和預(yù)測(cè)。
因此 state 是只讀的!唯一改變 state 的方法就是觸發(fā) action,action 是一個(gè)用于描述已發(fā)生事件的普通對(duì)象。
Redux 期望所有狀態(tài)更新都是使用不可變的方式,因此,每一次的 state 變更,不會(huì)修改原對(duì)象,而是修改前一個(gè)狀態(tài)(state)的克隆對(duì)象,以此來(lái)保證不可變性和正確性,同時(shí)記錄每一次變化的 state。
(4) 純函數(shù)更新 state
純函數(shù): 相同的輸入,總是會(huì)得到相同的輸出,并且在執(zhí)行過(guò)程中沒(méi)有任何副作用的函數(shù)。
為了保證數(shù)據(jù)的改變正確性,以及滿(mǎn)足 state 不可變性的要求,因此引入了 純函數(shù) 作為更新?tīng)顟B(tài)的唯一方式。
React Hooks 的狀態(tài)管理就融合了 Redux 的設(shè)計(jì)思想,畢竟把 Redux 的作者 Dan Abramov 都直接挖過(guò)去了!
二、案例實(shí)踐
下面講講如何接入一個(gè)全新的項(xiàng)目中,以 create-react-app[1] 腳手架創(chuàng)建的項(xiàng)目為例子。
借助 @redux/toolkit,不再需要刻意關(guān)心如何組織編寫(xiě) Reducer、Action creator、Action Type 等內(nèi)容,同時(shí),默認(rèn)就融合支持 異步 Thunks。
再結(jié)合 React 16.x 中的 Hooks,使用 useSelector()、useDispatch() 在任意組件中消費(fèi) Store。
1、初始化項(xiàng)目
首先是借助 create-react-app 初始化一個(gè) TS + React 環(huán)境的項(xiàng)目。
npx create-react-app craapp --template typescript
2、安裝 Redux 相關(guān)依賴(lài)
yarn add redux react-redux @reduxjs/toolkit
- redux: 核心狀態(tài)管理庫(kù)。
- react-redux: 用于 React 框架的橋接層。
- @reduxjs/toolkit: 降低 Redux 使用難度的助手。
3、 全局 Store 的創(chuàng)建
所有的狀態(tài)都放在了 Store 中,因此需要一個(gè)統(tǒng)一的地方來(lái)管理,以一個(gè)計(jì)數(shù)器為例,在 ./src/store 下的文件結(jié)構(gòu)如下:
.
├── index.ts // store 實(shí)例,導(dǎo)出 state 和 dispatch 類(lèi)型
└── reducers // 集合所有的 reducer
├── counter.ts // 用于計(jì)數(shù)器的 reducer、action、selector
└── index.ts // 導(dǎo)出 rootReducers,用于整合所有的 reducer
(1) store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import rootReducers from "./reducers"; // 引入 reducer 的集合
// 實(shí)例化 store,全局唯一
const store = configureStore({
reducer: rootReducers,
});
// 導(dǎo)出 Store 中的狀態(tài)(state)類(lèi)型
export type RootState = ReturnType;
// 導(dǎo)出更改狀態(tài)的 Dispatch 方法類(lèi)型
export type AppDispatch = typeof store.dispatch;
// 默認(rèn)導(dǎo)出 store,用于全局的 Provieder 消費(fèi)
export default store;
(2) store/reducers/index.ts
import {combineReducers} from '@reduxjs/toolkit'
import counterSlice from './counter' // 可以引入各種 reducer
const rootReducers = combineReducers({
counter: counterSlice // 這里通過(guò) MAP 形式,自定義不同 reducer 的“命名空間”
// ... 可以在這里擴(kuò)展添加任意的 reducer
})
// 默認(rèn)導(dǎo)出,給 configureStore 消費(fèi)
export default rootReducers
(3) store/reducers/counter.ts
接下來(lái)看看怎么便捷的創(chuàng)建一個(gè) Reducer,以前使用 Redux 總是需要手動(dòng)創(chuàng)建多個(gè)文件,reducer、action、action creator,但現(xiàn)在可以直接借助 @redux/toolkit 統(tǒng)一的放在一個(gè)文件中,結(jié)構(gòu)化的去描述 Redux 中的 action 和 redcuer。
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { AppDispatch, RootState } from ".."; // 在 store/index.ts 中聲明的類(lèi)型
// 借助 createSlice 創(chuàng)建 reducer、action
const CounterSlice = createSlice({
name: "counter", // 生成 Action type 的前綴,例如:counter/increment
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1; // 這里默認(rèn)通過(guò)了 immer 處理,不會(huì)修改原 state
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction) => {
state.value += action.payload;
},
decrementByAmount: (state, action: PayloadAction) => {
state.value -= action.payload;
},
},
});
// Action Creator 用于執(zhí)行返回描述如何更新 state 的 Action
export const { increment, decrement, incrementByAmount, decrementByAmount } =
CounterSlice.actions;
// 異步 thunk,用于需要在更新數(shù)據(jù)前異步處理數(shù)據(jù)的情況
export const incrementAsync = (amount: number) => (dispatch: AppDispatch) => {
setTimeout(() => {
dispatch(incrementByAmount(amount));
}, 1500);
};
// Selector,作為 useSelector 讀取數(shù)據(jù)的函數(shù)參數(shù)
export const counterSelector = (state: RootState) => state.counter.value;
// Reducer,真正執(zhí)行修改 state 的純函數(shù)
export default CounterSlice.reducer;
如上的寫(xiě)法可以作為一種“模板”,毋須關(guān)心各種概念之間的組合,直接用就可以了!
console.log(CounterSlice)
/*
output:
{
name: 'counter',
actions : {
increment,
decrement,
incrementByAmount,
decrementByAmount
},
reducer
}
*/
上述 actions 中的函數(shù)就是 Action creator,例如執(zhí)行 increment() 返回的就是:
{type: 'counter/increment'}
執(zhí)行 incrementByAmount(5) 返回的是:
{type: 'counter/incrementByAmount', payload: 5}
4、組件中讀寫(xiě) Stoe
首先是需要將 Store 實(shí)例綁定到我們的應(yīng)用上。
在 ./src/index.tsx 中添加如下:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux"; // 引入 Provider,綁定 store 到應(yīng)用上
import store from "./store"; // 引入 store 實(shí)例
import App from "./App";
import "./index.css";
ReactDOM.render(
,
{/* 綁定 store */}
document.getElementById("root")
);
結(jié)合 react-redux 提供的 useSelector() 和 useDispatch() 可以在我們自定義的 Counter 組件中消費(fèi) counter 狀態(tài)(數(shù)據(jù))。
//文件位置:src/pages/counter/index.tsx
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { decrement, incrementAsync, counterSelector } from "@/store/reducers/counter";
import "./index.scss";
const CounterPage = () => {
const count = useSelector(counterSelector) // 讀取 count 值
const dispatch = useDispatch() // 獲得 dispatch,結(jié)合 action 就可更新 state
return (
{/* 同步 - */}
dispatch(decrement())}>
-
{`${count}`}
{/* 異步 + */}
dispatch(incrementAsync(5))}>
+
);
};
export default CounterPage;
實(shí)際效果:
計(jì)時(shí)器效果演示
縱觀整個(gè)案例,相較于不使用 @redux/toolkit 顯著提升了研發(fā)的效率,降低了研發(fā)的使用心智負(fù)擔(dān)!
三、擴(kuò)展知識(shí)
1、 @redux/toolkit API
在上述的實(shí)際案例中,用到了如下 API:
- configureStore(): 簡(jiǎn)化 Store 的創(chuàng)建,默認(rèn)創(chuàng)建了執(zhí)行異步的中間件,自動(dòng)啟用 redux devtool。
- combineReducers():簡(jiǎn)化合并 reducer 的操作,并自動(dòng)注入 state 和 action。
- createSlice():簡(jiǎn)化并統(tǒng)一創(chuàng)建 action creator、reducer。
上述仨 API 可以滿(mǎn)足大部分的場(chǎng)景,在此工具輔助下,極大程度上減少了 TypeScript 類(lèi)型定義的工作。
當(dāng)然,想要了解更多關(guān)于 @redux/toolkit 便捷的 API,推薦閱讀官方文檔:
- @redux/tookit 的 API 使用手冊(cè)[2]。
- @redux/tookit 的 API 使用手冊(cè) —— TypeScript 類(lèi)型相關(guān)[3]。
2、 Redux 的狀態(tài)變更
如果對(duì) Redux 的狀態(tài)更新過(guò)程和原理感興趣,這里十分推薦閱讀:
- Redux如何實(shí)現(xiàn)state變化觸發(fā)頁(yè)面渲染?[4]。
3、 Redux 的同步和異步數(shù)據(jù)流
同步數(shù)據(jù)流:
Redux 的同步數(shù)據(jù)流動(dòng)圖鏈接:https://umapu.cn/imgs/202203/8c767817cfd66ba6c45276c52e98c8b2.gif。
異步數(shù)據(jù)流:
Redux 的異步數(shù)據(jù)流動(dòng)圖鏈接:https://umapu.cn/imgs/202203/e7edf1f729772323b2aebaae824716eb.gif。
四、總結(jié)
React 項(xiàng)目選擇 Redux 作為全局的狀態(tài)管理還是非常推薦的,結(jié)合 React 16.x 的 Hooks 狀態(tài)更新,非常方便,也符合函數(shù)組件的編碼風(fēng)格,再瞅瞅 React 的 useContext 和 useReducer,是不是會(huì)有一種 React 和 Redux 就是倆親兄弟的感覺(jué)?
簡(jiǎn)單總結(jié)一下:
- 推薦在 React 項(xiàng)目中使用 Redux 作為狀態(tài)管理。
- 需要掌握 Redux 中的設(shè)計(jì)思想。
- 推薦使用 @redux-toolkit,可降低心智負(fù)擔(dān),顯著提升研發(fā)效率。
- 當(dāng)掌握 @redux-toolkit 后,可補(bǔ)充閱讀 Redux 原本的 API,思考一下為什么 @redux-toolkit 要這么做?
參考資料
[1]create-react-app: https://create-react-app.dev。
[2]@redux/tookit 的 API 使用手冊(cè): https://redux-toolkit.js.org/usage/usage-guide。
[3]@redux/tookit 的 API 使用手冊(cè) —— TypeScript 類(lèi)型相關(guān): https://redux-toolkit.js.org/usage/usage-with-typescript。
[4]Redux如何實(shí)現(xiàn)state變化觸發(fā)頁(yè)面渲染?: https://juejin.cn/post/6945808822308962317。
本文標(biāo)題:用 Redux 做狀態(tài)管理,真的很簡(jiǎn)單!
文章分享:http://m.fisionsoft.com.cn/article/ccogech.html


咨詢(xún)
建站咨詢(xún)
