用最基礎的方法講解 Redux 實現原理?說白了實際上是我能力有限,只能用最基礎的方法來說解,爲了講的更加清楚,文章可能比較拖沓。不過我相信,不是很瞭解 Redux 的同窗,看完我今天分享的文章必定會有所收穫!javascript
這不是我今天要說的重點,想知道什麼是 Redux 點擊傳送門html
在開始以前我想先講一種經常使用的設計模式:觀察者模式。先來講一下我對
觀察者模式
的我的理解:觀察者模式(Publish/Subscribe)模式。對於這種模式很清楚的同窗下面這段代碼能夠跳過。若是你還不清楚,你能夠試着手敲一遍下面的代碼!!java
觀察者模式,基於一個主題/事件通道,但願接收通知的對象(稱爲subscriber)經過自定義事件訂 閱主題,經過deliver發佈主題事件的方式被通知。就和用戶訂閱微信公衆號道理同樣,只要發佈,用戶就能接收到最新的內容。git
/** * 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 方法 。github
爲啥要講這個發佈訂閱模式呢?由於搞清楚了這個模式那麼接下來你讀該文章就會感受更加清晰。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改變狀態的思路。可是光一個發佈訂閱仍是不夠的,不可能改變一個狀態須要去定義這麼多方法。因此咱們把他封裝起來。數組
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.微信
實現了 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
什麼意思,對於這種英語上來我就是有道翻譯一下固然這個翻譯感受並沒什麼做用,
找一找中文 Redux 官網,他是這樣說的:
之因此將這樣的函數稱之爲reducer,是由於這種函數與被傳入 Array.prototype.reduce(reducer, ?initialValue) 裏的回調函數屬於相同的類型。保持 reducer 純淨很是重要。永遠不要在 reducer 裏作這些操做。
誒,這個翻譯彷佛就清楚了不少。正以下面評論者說的同樣 靈感來自於數組中reduce方法,是一種運算合成。那麼說到這裏我就來介紹一下 reduce。
話很少說直接上代碼
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
/*該減速做用有四個參數: *累加器(acc) *當前價值(cur) *當前指數(idx) *源數組(src) *您的reducer函數的返回值被分配給累加器,其值在整個陣列的每次迭代中被記住,並最終成爲最終的單個結果值。 */
複製代碼
具體參數介紹
callback
函數在數組中的每一個元素上執行,有四個參數:
accumulator
累加器累加回調的返回值; 它是先前在回調調用中返回的累計值,或者initialValue,若是提供(參見下文)。
currentValue
當前元素在數組中處理。
currentIndex可選的
數組中正在處理的當前元素的索引。若是initialValue提供了an,則從索引0開始,不然從索引1開始
array可選的
該陣列reduce()被召喚。
initialValue可選的
用做第一次調用的第一個參數的值callback。若是未提供初始值,則將使用數組中的第一個元素。調用reduce()沒有初始值的空數組是一個錯誤。
複製代碼
這個方法相對比 forEach, map, filter 這個理解起來仍是算比較困難的。也能夠看 MDN 的 Array.prototype.reduce() 詳細介紹
注:首先感謝下面評論者 panda080 的指導,受他的建議,我從新去 Rudex 官網尋找。經過學習本身也更加的理解了 reducer 和 reduce reducer官網
ps:理解完以後,其實我的以爲 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。
今天也只是講了他的一小部分。本身也在努力學習中,但願從此能分享更多的見解,並和你們深刻探討。
上述每一個案例,和代碼我都託管在 github 上了,分享給你們能夠直接打開即用。github 傳送門
全文章,若有錯誤或不嚴謹的地方,請務必給予指正,謝謝!
參考: