Redux
也是我列在 THE LAST TIME
系列中的一篇,因爲如今正在着手探究關於我目前正在開發的業務中狀態管理的方案。因此,這裏打算先從 Redux
中學習學習,從他的狀態中取取經。畢竟,成功老是須要站在巨人的肩膀上不是。前端
話說回來,都 2020 年了還在寫 Redux
的文章,真的是有些過期了。不過呢,當時 Redux
孵化過程當中必定也是回頭看了 Flux
、CQRS
、ES
等。react
本篇先從 Redux
的設計理念到部分源碼分析。下一篇咱們在注重說下 Redux
的 Middleware
工做機制。至於手寫,推薦磚家大佬的:徹底理解 redux(從零實現一個 redux)git
Redux
Redux
並非什麼特別 Giao 的技術,可是其理念真的提的特別好。github
說透了,它就是一個提供了 setter
、getter
的大閉包,。外加一個 pubSub
。。。另外的什麼 reducer
、middleware
仍是 action
什麼的,都是基於他的規則和解決用戶使用痛點而來的,僅此而已。下面咱們一點點說。。。web
在 jQuery 時代的時候,咱們是面向過程開發,隨着 react 的普及,咱們提出了狀態驅動 UI 的開發模式。咱們認爲: Web 應用就是狀態與 UI 一一對應的關係。編程
可是隨着咱們的 web 應用日趨的複雜化,一個應用所對應的背後的 state 也變的愈來愈難以管理。redux
而 Redux
就是咱們 Web 應用的一個狀態管理方案。api
如上圖所示,store 就是 Redux
提供的一個狀態容器。裏面存儲着 View 層所須要的全部的狀態(state)。每個 UI 都對應着背後的一個狀態。Redux
也一樣規定。一個 state 就對應一個 View。只要 state 相同,View 就相同。(其實就是 state 驅動 UI)。數組
Redux
如上所說,咱們如今是狀態驅動 UI,那麼爲何須要 Redux
來管理狀態呢?react 自己就是 state drive view 不是。微信
緣由仍是因爲如今的前端的地位已經愈發的不同啦,前端的複雜性也是愈來愈高。一般一個前端應用都存在大量複雜、無規律的交互。還伴隨着各類異步操做。
任何一個操做均可能會改變 state,那麼就會致使咱們應用的 state 愈來愈亂,且被動緣由愈發的模糊。咱們很容易就對這些狀態什麼時候發生、爲何發生、怎麼發生而失去控制。
如上,若是咱們的頁面足夠複雜,那麼view
背後state
的變化就可能呈現出這個樣子。不一樣的 component
之間存在着父子、兄弟、子父、甚至跨層級之間的通訊。
而咱們理想中的狀態管理應該是這個樣子的:
單純的從架構層面而言,UI 與狀態徹底分離,而且單向的數據流確保了狀態可控。
而 Redux
就是作這個的!
State
的變化可預測
下面簡單介紹下 Redux
中的幾個概念。其實初學者每每就是對其概念而困惑。
保存數據的地方,你能夠把它當作一個容器,整個應用只能有一個
Store
。
某一個時刻,存儲着的應用狀態值
View 發出的一種讓
state
發生變化的通知
能夠理解爲
Action
的工廠函數
View 發出
Action
的媒介。也是惟一途徑
根據當前接收到的
Action
和State
,整合出來一個全新的State
。注意是須要是純函數
Redux
的使用,基於如下三個原則
單一數據源這或許是與 Flux 最大的不一樣了。在 Redux
中,整個應用的 state
都被存儲到一個object
中。固然,這也是惟一存儲應用狀態的地方。咱們能夠理解爲就是一個 Object tree
。不一樣的枝幹對應不一樣的 Component
。可是歸根結底只有一個根。
也是受益於單一的 state tree
。之前難以實現的「撤銷/重作」甚至回放。都變得輕鬆了不少。
惟一改變 state
的方法就是 dispatch
一個 action
。action
就是一個令牌而已。normal Object
。
任何 state
的變動,均可以理解爲非 View
層引發的(網絡請求、用戶點擊等)。View
層只是發出了某一中意圖。而如何去知足,徹底取決於 Redux
自己,也就是 reducer。
store.dispatch({
type:'FETCH_START',
params:{
itemId:233333
}
})
複製代碼
所謂純函數,就是你得純,別變來變去了。書面詞彙這裏就不作過多解釋了。而這裏咱們說的純函數來修改,其實就是咱們上面說的 reducer
。
Reducer
就是純函數,它接受當前的 state
和 action
。而後返回一個新的 state
。因此這裏,state
不會更新,只會替換。
之因此要純函數,就是結果可預測性。只要傳入的 state
和 action
一直,那麼就能夠理解爲返回的新 state
也老是同樣的。
Redux
的東西遠不止上面說的那麼些。其實還有好比 middleware、actionCreator 等等等。其實都是使用過程當中的衍生品而已。咱們主要是理解其思想。而後再去源碼中學習如何使用。
Redux 源碼自己很是簡單,限於篇幅,咱們下一篇再去介紹
compose
、combineReducers
、applyMiddleware
Redux
源碼自己就是很簡單,代碼量也不大。學習它,也主要是爲了學習他的編程思想和設計範式。
固然,咱們也能夠從 Redux
的代碼裏,看看大佬是如何使用 ts 的。因此源碼分析裏面,咱們還回去花費很多精力看下 Redux
的類型說明。因此咱們從 type 開始看
看類型聲明也是爲了學習Redux
的 ts 類型聲明寫法。因此類似聲明的寫法形式咱們就不重複介紹了。
類型聲明也沒有太多的須要去說的邏輯,因此我就寫註釋上吧
// Action的接口定義。type 字段明確聲明
export interface Action<T = any> {
type: T
}
export interface AnyAction extends Action {
// 在 Action 的這個接口上額外擴展的另一些任意字段(咱們通常寫的都是 AnyAction 類型,用一個「基類」去約束必須帶有 type 字段)
[extraProps: string]: any
}
export interface ActionCreator<A> {
// 函數接口,泛型約束函數的返回都是 A
(...args: any[]): A
}
export interface ActionCreatorsMapObject<A = any> {
// 對象,對象值爲 ActionCreator
[key: string]: ActionCreator<A>
}
複製代碼
// 定義的一個函數,接受 S 和繼承 Action 默認爲 AnyAction 的 A,返回 S
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined,
action: A
) => S
// 能夠理解爲 S 的 key 做爲ReducersMapObject的 key,而後 value 是 Reducer的函數。in 咱們能夠理解爲遍歷
export type ReducersMapObject<S = any, A extends Action = Action> = {
[K in keyof S]: Reducer<S[K], A>
}
複製代碼
上面兩個聲明比較簡單直接。下面兩個稍微麻煩一些
export type StateFromReducersMapObject<M> = M extends ReducersMapObject<
any,
any
>
? { [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
: never
export type ReducerFromReducersMapObject<M> = M extends {
[P in keyof M]: infer R
}
? R extends Reducer<any, any>
? R
: never
: never
複製代碼
上面兩個聲明,我們來解釋其中第一個吧(稍微麻煩些)。
StateFromReducersMapObject
添加另外一個泛型
M
約束
M
若是繼承
ReducersMapObject<any,any>
則走
{ [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
的邏輯
never
。啥也不是
{ [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
很明顯,這就是一個對象,
key
來自
M
對象裏面,也就是
ReducersMapObject
裏面傳入的
S
。
key
對應的
value
就是須要判斷
M[P]
是否繼承自
Reducer
。不然也啥也不是
infer
關鍵字和
extends
一直配合使用。這裏就是指返回
Reducer
的這個
State
的類型
types
目錄裏面其餘的好比 store
、middleware
都是如上的這種聲明方式,就再也不贅述了,感興趣的能夠翻閱翻閱。而後取其精華的應用到本身的 ts 項目裏面
能夠看到,整個createStore.ts 就是一個createStore 函數。
三個參數:
reducer
:就是 reducer,根據 action 和 currentState 計算 newState 的純 Function
preloadedState
:initial State
enhancer
:加強store的功能,讓它擁有第三方的功能
createStore
裏面就是一些閉包函數的功能整合。
// A extends Action
dispatch({ type: ActionTypes.INIT } as A)
複製代碼
這個方法是Redux
保留用的,用來初始化State
,其實就是dispatch
走到咱們默認的 switch case default
的分支裏面獲取到默認的 State
。
const store = ({
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
複製代碼
ts 的類型轉換語法就不說了,返回的對象裏面包含dispatch
、subscribe
、getState
、replaceReducer
、[$$observable]
.
這裏咱們簡單介紹下前三個方法的實現。
function getState(): S {
if (isDispatching) {
throw new Error(
`我 reducer 正在執行,newState 正在產出呢!如今不行`
)
}
return currentState as S
}
複製代碼
方法很簡單,就是 return currentState
subscribe
的做用就是添加監聽函數listener
,讓其在每次dispatch action
的時候調用。
返回一個移除這個監聽的函數。
使用以下:
const unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
unsubscribe();
複製代碼
function subscribe(listener: () => void) {
// 若是 listenter 不是一個 function,我就報錯(其實 ts 靜態檢查能檢查出來的,但!那是編譯時,這是運行時)
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 同 getState 一個樣紙
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://`Redux`.js.org/api/store#subscribelistener for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
// 直接將監聽的函數放進nextListeners裏
nextListeners.push(listener)
return function unsubscribe() {// 也是利用閉包,查看是否以訂閱,而後移除訂閱
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://`Redux`.js.org/api/store#subscribelistener for more details.'
)
}
isSubscribed = false//修改這個訂閱狀態
ensureCanMutateNextListeners()
//找到位置,移除監聽
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
複製代碼
一句話解釋就是在 listeners 數據裏面添加一個函數
再來講說這裏面的ensureCanMutateNextListeners
,不少 Redux
源碼都麼有怎麼說起這個方法的做用。也是讓我有點困惑。
這個方法的實現很是簡單。就是判斷當前的監聽數組裏面是否和下一個數組相等。若是是!則 copy 一份。
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
複製代碼
那麼爲何呢?這裏留個彩蛋。等看完 dispatch
再來看這個疑惑。
function dispatch(action: A) {
// action必須是個普通對象
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// 必須包含 type 字段
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// 同上
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
// 設置正在 dispatch 的 tag 爲 true(解釋了那些判斷都是從哪裏來的了)
isDispatching = true
// 經過傳入的 reducer 來去的新的 state
// let currentReducer = reducer
currentState = currentReducer(currentState, action)
} finally {
// 修改狀態
isDispatching = false
}
// 將 nextListener 賦值給 currentListeners、listeners (注意回顧 ensureCanMutateNextListeners )
const listeners = (currentListeners = nextListeners)
// 挨個觸發監聽
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
複製代碼
方法很簡單,都寫在註釋裏了。這裏咱們再回過頭來看ensureCanMutateNextListeners
的意義
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function subscribe(listener: () => void) {
// ...
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action: A) {
// ...
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// ...
return action
}
複製代碼
從上,代碼看起來貌似只要一個數組來存儲listener
就能夠了。可是事實是,咱們偏偏就是咱們的 listener
是能夠被 unSubscribe
的。並且 slice
會改變原數組大小。
因此這裏增長了一個 listener
的副本,是爲了不在遍歷listeners
的過程當中因爲subscribe
或者unsubscribe
對listeners
進行的修改而引發的某個listener
被漏掉了。
限於篇幅,就暫時寫到這吧~
其實後面打算重點介紹的 Middleware
,只是中間件的一種更規範,甚至咱們能夠理解爲,它並不屬於 Redux
的。由於到這裏,你已經徹底能夠本身寫一份狀態管理方案了。
而 combineReducers
也是我認爲是費巧妙的設計。因此這些篇幅,就放到下一篇吧~
公衆號【全棧前端精選】 | 我的微信【is_Nealyang】 |
---|---|