用最基礎的方法講解 Redux 實現原理
用最基礎的方法講解 Redux 實現原理?說白了其實是我能力有限,只能用最基礎的方法來講解,為了講的更加清楚,文章可能比較拖沓。不過我相信,不是很瞭解 Redux 的同學,看完我今天分享的文章一定會有所收穫!
什麼是 Redux ?
這不是我今天要說的重點,想知道什麼是 Redux點選傳送門
開始
在開始之前我想先講一種常用的設計模式:觀察者模式。先來說一下我對 觀察者模式
的個人理解:觀察者模式又稱“釋出-訂閱(Publish/Subscribe)模式。對於這種模式很清楚的同學下面這段程式碼可以跳過。如果你還不清楚,你可以試著手敲一遍下面的程式碼!!
觀察者模式
觀察者模式,基於一個主題/事件通道,希望接收通知的物件(稱為subscriber)通過自定義事件訂 閱主題,通過deliver釋出主題事件的方式被通知。就和使用者訂閱微信公眾號道理一樣,一個公眾號可以被多個使用者同時訂閱,當公眾號有新增內容時候,只要釋出就好了,使用者就能接收到最新的內容。
/** * describe: 實現一個觀察者模式 */ let data = { hero: '鳳凰', }; //用來儲存 訂閱者 的陣列 let subscribers = []; //訂閱 新增訂閱者 方法 const addSubscriber = function(fn) { subscribers.push(fn) } //釋出 const deliver = function(name) { data.hero = name; //當資料發生改變,呼叫(通知)所有方法(訂閱者) for(let i = 0; i<subscribers.length; i++){ const fn = subscribers[i] fn() } } //通過 addSubscriber 發起訂閱 addSubscriber(() => { console.log(data.hero) }) //改變data,就會自動列印名稱 deliver('發條') deliver('狐狸') deliver('卡牌') 複製程式碼
這個釋出訂閱通過 addSubscriber 來儲存訂閱者(方法fn),當通過呼叫 deliver 來改變資料的時候,就會自動遍歷 addSubscriber 來執行裡面的 fn 方法 。
為啥要講這個釋出訂閱模式呢?因為搞清楚了這個模式那麼 Redux 的原理就會水到渠成。
Redux 起步
首先我們把上面那個釋出訂閱程式碼優化一下,順便改一下命名,為什麼要改命名?主要是緊跟 Redux 的步伐。讓同學們更加眼熟。
let state = {hero: '鳳凰'}; let subscribers = []; //訂閱 定義一個 subscribe const subscribe = (fn) => { subscribers.push(fn) } //釋出 const dispatch = (name) => { state.hero = name; //當資料發生改變,呼叫(通知)所有方法(訂閱者) subscribers.forEach(fn=>fn()) } //通過 subscribe 發起訂閱 subscribe(() => { console.log(state.hero) }) //改變state狀態,就會自動列印名稱 //這裡要注意的是,state狀態不能直接去修改 dispatch('發條') dispatch('狐狸') dispatch('卡牌') 複製程式碼
現在這樣一改是不是很眼熟了,沒錯這就是一個類似redux改變狀態的思路。但是光一個釋出訂閱還是不夠的,不可能改變一個狀態需要去定義這麼多方法。所以我們把他封裝起來。
creatStore 方法
const creatStore = (initState) => { let state = initState; let subscribers = []; //訂閱 定義一個 subscribe const subscribe = (fn) => { subscribers.push(fn) } //釋出 const dispatch = (currentState) => { state = currentState; //當資料發生改變,呼叫(通知)所有方法(訂閱者) subscribers.forEach(fn=>fn()) } // 這裡需要新增這個獲取 state 的方法 const getState = () => { return state; } return { subscribe, dispatch, getState, } } 複製程式碼
這樣就建立好了一個 createStore 方法。沒有什麼新東西,就傳進去一個初始狀態,然後在返回 subscribe, dispatch, getState 三大方法。這裡新增了個 getState 方法,程式碼很簡單就是一個 return state 為了獲取 state.
creatStore 使用
實現了 createStore 下面我們來試試如何使用他,那就拿那個非常經典的案例--計數器來試試
let initState = { num: 0, } const store = creatStore(initState); //訂閱 store.subscribe(() => { let state = store.getState() console.log(state.num) }) // +1 store.dispatch({ num: store.getState().num + 1 }) //-1 store.dispatch({ num: store.getState().num - 1 }) 複製程式碼
這個樣子又接近了一點 Redux 的模樣。 不過這樣有個問題。如果你使用 store.dispatch 方法時,中間萬一寫錯了或者傳了個其他東西那就比較麻煩了。就比如下面這樣:

其實我是想 +1,+1,-1 最後應該是 1 (初始 num 為0)!但是由於寫錯了一個導致後面的都會錯。而且他還有個問題就是可以隨便的給一個新的狀態。那麼就顯得不那麼單純了。比如下面這樣:

因為惡意修改 num 為 String 型別,導致後面在使用 dispatch 由於 num 不再是 Number 型別,導致打印出 NaN,這就不是我們想要的啦。所以我們要在改造一下,讓 dispatch 變得單純一些。那要怎麼做呢?我們請一個管理者來幫我們管理,暫且給他命名 reducer
什麼是 reducer
reducer 相當於是個管理者,然後我們每次想做什麼就去通知管理者,讓他在來根據我們說的去做。如果我們不小心說錯了,那麼他就不會去做。直接按預設的事情來。噔噔蹬蹬 reducer 登場!!
function reducer(state, action) { //通過傳進來的 action.type 讓管理者去匹配要做的事情 switch (action.type){ case 'add': return { ...state, count: state.count + 1 } case 'minus': return { ...state, count: state.count - 1 } // 沒有匹配到的方法 就返回預設的值 default: return state; } } 複製程式碼
增加了這個管理者,那麼我們就要重新來寫一下之前的 createStroe 方法了:把 reducer 放進去
const creatStore = (reducer,initState) => { let state = initState; let subscribers = []; //訂閱 定義一個 subscribe const subscribe = (fn) => { subscribers.push(fn) } //釋出 const dispatch = (action) => { state = reducer(state,action); subscribers.forEach(fn=>fn()) } const getState = () => { return state; } return { subscribe, dispatch, getState, } } 複製程式碼
很簡單的一個修改,為了讓你們方便看出修改的地方,和區別,我特意重新碼了這兩個前後的方法對比,如下圖

好,接下來我們試試添加了管理者的 creatStore 效果如何。
function reducer(state, action) { //通過傳進來的 action.type 讓管理者去匹配要做的事情 switch (action.type){ case 'add': return { ...state, num: state.num + 1 } case 'minus': return { ...state, num: state.num - 1 } // 沒有匹配到的方法 就返回預設的值 default: return state; } } let initState = { num: 0, } const store = creatStore(reducer,initState); //訂閱 store.subscribe(() => { let state = store.getState() console.log(state.num) }) 複製程式碼
為了看清楚結果,dispatch(訂閱)我直接再控制檯輸出,如下圖:

效果很好,我們不會再因為寫錯,而出現 NaN 或者其他不可描述的問題。現在這個 dispatch 比較純粹了一點。
我們只是給他一個 type ,然後讓管理者自己去幫我們處理如何更改狀態。如果不小心寫錯,或者隨便給個 type 那麼管理者匹配不到那麼這個動作那麼我們這次 dispatch 就是無效的,會返回我們自己的預設 state。
好叻,現在這個樣子基本上就是我腦海中第一次使用 redux 看到的樣子。那個時候我使用起來都非常困難。當時勉強實現了一下這個計數器 demo 我就默默的關閉了 vs code。
接下來我們再完善一下這個 reducer,給他再新增一個方法。並且這次我們再給 state 一個
function reducer(state, action) { //通過傳進來的 action.type 讓管理者去匹配要做的事情 switch (action.type){ case 'add': return { ...state, num: state.num + 1 } case 'minus': return { ...state, num: state.num - 1 } // 增加一個可以傳參的方法,讓他更加靈活 case 'changeNum': return { ...state, num: state.num + action.val } // 沒有匹配到的方法 就返回預設的值 default: return state; } } let initState = { num: 0, } const store = creatStore(reducer,initState); //訂閱 store.subscribe(() => { let state = store.getState() console.log(state.num) }) 複製程式碼
控制檯再使用一次新的方法:

好叻,這樣是不是就讓 dispatch 更加靈活了。
現在我們再 reducer 中就寫了 3 個方法,但是實際專案中,方法一定是很多的,那麼都這樣寫下去,一定是不利於開發和維護的。那麼這個問題就留給大家去思考一下。
提示:Redux 也知道這一點,所以他提供了 combineReducers
去實現這個模式。這是一個高階 Reducer 的示例,他接收一個拆分後 reducer 函式組成的物件,返回一個新的 Reducer 函式。
思考完之後可以參考redux 中文文件 的combineReducers介紹
具體實現原理我將會在下次分享。
總結
Redux 這個專案裡,有很多非常巧妙的方法,很多地方可以借鑑。畢竟這可是在 github 上又 47W+ 的 Star。
今天也只是講了他的一小部分。自己也在努力學習中,希望今後能分享更多的看法,並和大家深入探討。