*本文為公司前端團隊內部分享的文案,是在網上剽竊、參考、學習了諸多文章和代碼后,加上自己的理解而成,如果對你有所幫助,不生榮幸。
最終代碼在github,地址:[https://github.com/fujiazhang/Redux](https://github.com/fujiazhang/Redux)*
正文:
因為一開始我是做vue技術棧,剛接觸react的時候,對于redux有很多不理解的地方,因為vuex封裝的很好,而react-redux就很簡陋,對于redux也有很多不理解的地方,但是照著文檔寫也ok,但是總歸理解不深入,借著這次分享的機會,參考很多優秀的文章代碼,總算是搞懂了redux, 當然本次分享是是redux,不是react-redux,。
首先, redux是一種架構,他和react沒有任何關系,可以用在任何東西,像是什么 connect之類是屬于react-redux,ok, 現在請忘掉reducer、store、dispatch、middleware這些單詞,我們一步步來推導。
## 一、redux的本質就是:就是使用store來管理state的數據管理器
我們首先定義一個狀態管理器:
```
let state = {
name: 'zhangsan'
}
```
ok, 接下來我們來修改和使用它
```
console.log(state.name)
```
now, 我們現在有了一個狀態管理器了,現在一步步來完善這個狀態管理器。
ok,我們現在面臨第一個問題,我們沒辦法在修改的時候,知道我們的狀態被修改了,比如我頁面上有地方引用了這個數據,但是被修改了,卻不知道(無法更新同步view),這里使用訂閱/發布模式來解決這個修改了數據需要被通知(知道被修改了)的這個問題。
```
let state = {
name: 'zhangsan'
}
//保存訂閱者數據集合
const liesteners = []
//訂閱
function subscribe(liestener) {
liesteners.push(liestener)
}
function changeState(newState) {
state = newState
//發布
liesteners.forEach(f = >f())
}
```
ok,代碼如上,我們來使用下
```
subscribe(() = >{
console.log('state is changed', state)
})
//更改狀態
changeState({
name: 'lisi'
})
```
現在我們可以看到,我們修改 name 的時候,會輸出相應的 state值, 解決了當數據更改時,無法知道修改(或者說收到通知)這個問題,現在我們有新的問題:
* 這個狀態管理器只能管理我這一個對象,不通用
* 公共的代碼要封裝起來
我們來抽象一下:
```
function createStore(initSate) {
let state = initSate
const liesteners = [] //訂閱集合
function changeState(newState) {
state = newState
/*發布*/
liesteners.forEach(f = >f())
}
/*訂閱*/
function subscribe(f) {
liesteners.push(f)
}
function getStore() {
return state
}
return {
getStore,
changeState,
subscribe
}
}
```
ok, 我們來試試:
```
store.subscribe(()=> { console.log('state is changed:', store.getStore()) })
store.changeState({ name: 'fujiazhang' })
store.changeState({ name: 'guolei' })
store.changeState({ name: 'yuanying' })
```
輸入如下:

嗯,不錯,我們來試試來使用這個狀態管理器管理多個狀態
```
const store = createStore(initSate)
store.subscribe(() = >{
console.log('state is changed:', store.getStore())
})
store.changeState({...store.getStore(),
no1: 'zhangsan'
}) store.changeState({...store.getStore(),
no2: 'lisi'
}) store.changeState({...store.getStore(),
no3: 'wangwu'
})
```
運行輸出結果如下:

嗯,很爽,一切如我們想象的一致。
但是,但是,我們發現有一個嚴重的問題,我的傳入修改的對象是任何值,隨意傳,想傳啥,這肯定是不是我們所期望的,我們期望的肯定是,有固定的值,只允許,我們的nox 為傳入的值,我們可以這樣來解決:
1. 制定一個 state 修改計劃,告訴 store,我的修改計劃是什么。
2. 修改 store.changeState 方法,告訴它修改 state 的時候,按照我們的計劃修改。
我們來設置一個 plan 函數,接收現在的 state,和一個 action,返回經過改變后的新的 state。
```
function plan(state, action) {
switch (action.type) {
case 'CHANGE_NO_1':
return {...state,
no1: action.data
}
break;
default:
return state
break;
}
}
```
我們把這個計劃告訴 store,store.changeState 以后改變 state 要按照我的計劃來改。
現在的整體代碼如下:
```
function createStore(initSate) {
let state = initSate const liesteners = []
function changeState(action) {
state = plan(state, action) liesteners.forEach(f = >f())
}
function subscribe(f) {
liesteners.push(f)
}
function getStore() {
return state
}
return {
getStore,
changeState,
subscribe
}
}
function plan(state, action) {
switch (action.type) {
case 'CHANGE_NO_1':
return {...state,
no1: action.data
}
break;
default:
return state
break;
}
}
const initSate = {
no1: '',
no2: '',
no3: ''
}
const store = createStore(initSate)
store.subscribe(() = >{
console.log('state is changed:', store.getStore())
})
store.changeState({
type: 'CHANGE_NO_1',
data: 'zhangsan'
})
```
我們來運行一波,結果如下:

ok, 結果很理想,接下來我們把名字替換一下,到這里為止,我們已經實現了一個有計劃的狀態管理器!現在呢給 plan 和 changeState 改下名字好不好? plan 改成 reducer,changeState 改成 dispatch! ,嗯,和redux一致了。
現在的代碼如下:
```
function createStore(initSate) {
let state = initSate const liesteners = []
function dispatch(action) {
state = reducer(state, action) liesteners.forEach(f = >f())
}
function subscribe(f) {
liesteners.push(f)
}
function getStore() {
return state
}
return {
getStore,
dispatch,
subscribe
}
}
function reducer(state, action) {
switch (action.type) {
case 'CHANGE_NO_1':
return {...state,
no1: action.data
}
break;
default:
return state
break;
}
}
const initSate = {
no1: '',
no2: '',
no3: ''
}
const store = createStore(reducer, initSate)
store.subscribe(() = >{
console.log('state is changed:', store.getStore())
})
store.dispatch({
type: 'CHANGE_NO_1',
data: 'zhangsan'
})
```
ok, 到了這里,我們先把代碼分模塊拆開,現在一股腦的在一個index.js文件里,看著也累。組件化拆分后目錄如下:

我們繼續,到這里,其實已經有問題了,因為我們不可能把所有的計劃函數( reducer)寫在一個文件里,我們肯定是期望一個組建一個reducer, 比如支付組件一個reducer, 登陸組件一個reducer,隨著業務的累計,肯定會有大大大大量的reducer 所以,我們需要按組件來拆分出很多個 reducer 函數,然后通過一個函數來把他們合并起來。
現在我們來模擬修改一個state里的 兩個數據。
reducer1:
```
export function no1Reducer(state, action) {
switch (action.type) {
case 'CHANGE_NO_1':
return {...state,
no1: action.data
}
break;
default:
return state
break;
}
}
```
reducer2
```
export function no2Reducer(state, action) {
switch (action.type) {
case 'CHANGE_NO_2':
return {...state,
no2: action.data
}
break;
default:
return state
break;
}
}
```
ok, 我們現在來實現一個combineReducer函數,大概這樣用
~~~
const reducer = combineReducers({
counter: counterReducer,
info: InfoReducer
});
~~~
我們來實現這個combineReducer函數:
```
export function combineReducer(reducers) {
const reducersKey = Object.keys(reducers)
return (state = {}, action) = >{
const newState = {}
reducersKey.forEach(key = >{
let reducer = reducers[key] newState[key] = reducer(state[key], action)
}) return newState
}
}
```
來試試調用:

嗯,完美的實現了合并reducer,

我們把 reducer 按組件維度拆分了,通過 combineReducers 合并了起來。但是還有個問題, state 我們還是寫在一起的,這樣會造成 state 樹很龐大,不直觀,很難維護。我們需要拆分,一個 state,一個 reducer 寫一塊。

我們修改下 createStore 函數,增加一行`dispatch({ type: "" })

這里為什么這樣做就能ok:
1. createStore 的時候,用一個不匹配任何 type 的 action,來觸發`state = reducer(state, action)`
2. 因為 action.type 不匹配,每個子 reducer 都會進到 default 項,返回自己初始化的 state,這樣就獲得了初始化的 state 樹了。
目前為止,我們的redux已經實現的差不多了。
### 中間件 middleware
中間件 middleware 是 redux 中比較難理解的地方 ,**中間件的本質是對 dispatch 的擴展,或者說重寫,增強 dispatch 的功能!**
現在我們需有一個需求,就是想要在每次修改state的時候,把日志輸出到控制臺,包括修改的action, 修改前后的值(和taro做小程序里,里差不多的需求)
看圖

```
import { createStore, combineReducer } from "./redux";
import { no1Reducer } from "./reducer/no1";
import { no2Reducer } from "./reducer/no2";
import { asyncHanlde } from "./middleWares/asyncHanlde";
import { logger } from "./middleWares/logger";
import { timesmap } from "./middleWares/timesmap";
const reducer = combineReducer({
no1: no1Reducer,
no2: no2Reducer
})
const store = createStore(reducer)
const next = store.dispatch
const time = timesmap(store)
const asyncH = asyncHanlde(store)
const log = logger(store)
store.dispatch = time(asyncH(log((next))))
store.subscribe(() => { console.log('state is changed:', store.getStore()) })
store.dispatch({
type: 'CHANGE_NO_1',
data: 'zhangsan'
})
store.dispatch({
type: 'CHANGE_NO_2',
data: 'lisi'
})
```
其實到這里,就算ok了,你會發現和redux核心源碼已經90%相似了,還有一些函數,如applyMiddleware(對中間件的用法優化),還有如compoose函數,(將中間件的使用如 [a,b,c] 轉換成 a(b(c)))找各種形式,沒有實現,但是這個沒必要在深入,只是一些語法糖,還有一些函數參數驗證我們的reducer只能吃純函數、訂閱時間的退訂這些細枝末節的沒有實現,但是我們抓住主線就ok了。
###
總結
到了最后,我想把 redux 中關鍵的名詞列出來,你每個都知道是干啥的嗎?
* createStore
創建 store 對象,包含 getState, dispatch, subscribe, replaceReducer
* reducer
reducer 是一個計劃函數,接收舊的 state 和 action,生成新的 state
* action
action 是一個對象,必須包含 type 字段
* dispatch
`dispatch( action )`觸發 action,生成新的 state
* subscribe
實現訂閱功能,每次觸發 dispatch 的時候,會執行訂閱函數
* combineReducers
多 reducer 合并成一個 reducer
* middleware
擴展 dispatch 函數!
你再看 redux 流程圖,是不是大徹大悟了?

- 前言
- 工作中的一些記錄
- 破解快手直播間的webSocket的連接
- 快手「反」反爬蟲的研究記錄
- HTML AND CSS
- 遇到的一些還行的css筆試題
- css常見面試題
- JavaScript 深度剖析
- ES6到ESNext新特性
- 關于http與緩存
- 關于頁面性能
- 關于瀏覽器的重排(reflow、layout)與重繪
- 手寫函數節流
- 手寫promise
- 手寫函數防抖
- 手寫圖片懶加載
- 手寫jsonp
- 手寫深拷貝
- 手寫new
- 數據結構和算法
- 前言
- 時間復雜度
- 棧
- 隊列
- 集合
- 字典
- 鏈表
- 樹
- 圖
- 堆
- 排序
- 搜索
- Webpack
- Webpack原理與實踐
- Vue
- Vuejs的Virtual Dom的源碼實現
- minVue
- Vuex實現原理
- 一道關于diff算法的面試題
- Vue2源碼筆記:源碼目錄設計
- vue-router源碼分析(v4.x)
- React及周邊
- 深入理解redux(一步步實現一個 redux)
- React常見面試題匯總
- Taro、小程序等
- TypeScript
- CI/CD
- docker踩坑筆記
- jenkins
- 最后