用最基礎的方法講解 Redux 實現原理

寫在前面

用最基礎的方法講解 Redux 實現原理?說白了實際上是我能力有限,只能用最基礎的方法來說解,爲了講的更加清楚,文章可能比較拖沓。不過我相信,不是很瞭解 Redux 的同窗,看完我今天分享的文章必定會有所收穫!javascript

什麼是 Redux ?

這不是我今天要說的重點,想知道什麼是 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 起步

首先咱們把上面那個發佈訂閱代碼優化一下,順便改一下命名,爲何要改命名?主要是緊跟 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

什麼意思,對於這種英語上來我就是有道翻譯一下

固然這個翻譯感受並沒什麼做用,

找一找中文 Redux 官網,他是這樣說的:

之因此將這樣的函數稱之爲reducer,是由於這種函數與被傳入 Array.prototype.reduce(reducer, ?initialValue) 裏的回調函數屬於相同的類型。保持 reducer 純淨很是重要。永遠不要在 reducer 裏作這些操做。

誒,這個翻譯彷佛就清楚了不少。正以下面評論者說的同樣 靈感來自於數組中reduce方法,是一種運算合成。那麼說到這裏我就來介紹一下 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 在我學習的過程當中我把他認爲是個管理者(可能這個認爲是不正確的),而後咱們每次想作什麼就去通知管理者,讓他在來根據咱們說的去作。若是咱們不當心說錯了,那麼他就不會去作。直接按默認的事情來。噔噔蹬蹬 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 傳送門

全文章,若有錯誤或不嚴謹的地方,請務必給予指正,謝謝!

參考:

相關文章
相關標籤/搜索