redux直通自行車

前言

redux是個狀態(tài)管理工具蝴簇,提供簡化的api做到行為的可預測性杯活,由于react在組件之間通信繁瑣,狀態(tài)管理需要狀態(tài)提升造成代碼冗長等局限熬词,redux常與react結合使用旁钧,但是redux作為獨立的框架吸重,其實是可以和其他的庫配合使用的,為了不被react混淆了redux的使用方法歪今,我們這里結合jq來理解redux的使用

redux基本概念

redux有三個基本原則

  • 單一數據源:一個應用state只存在唯一一個對象樹當中(state狀態(tài)與試圖層級對應形成樹裝結構晤锹,所以稱為對象樹),由唯一的store統(tǒng)一管理
  • state是只讀的:redux規(guī)定不能直接修改state,修改state需要觸發(fā)action,action* 是一個包含type字段的普通函數彤委,用來描述發(fā)生的事情
  • 使用純函數修改state:通過action描述怎樣修改state,需要手動創(chuàng)建函數reducer(一個函數的返回結果只依賴于它的參數,并且在執(zhí)行過程里面沒有副作用或衡,我們就把這個函數叫做純函數)
const state ={
    visibilityFilter: 'SHOW_ALL',
    todos: [
      {
        text: 'Consider using Redux',
        completed: true,
      },
      {
        text: 'Keep all state in a single tree',
        completed: false
      }
    ]
}

我們可以根據之前定義的action更新state焦影,創(chuàng)建redux

const state ={
    visibilityFilter: 'SHOW_ALL',
    todos: [
      {
        text: 'Consider using Redux',
        completed: true,
      },
      {
        text: 'Keep all state in a single tree',
        completed: false
      }
    ]
}
function reducer(state=state,action){
   switch(action.type){
      case 'ADD_TODO':
        return Object.assign({},state,{
            todos:[
                ...state.todos,
                {text:action.text,completed:false}
            ]
        });
        case 'SET_VISIBILITY':
        return Object.assign({},state,{
           visibilityFilter:action.filter 
        }); 
      default:
        return state  
   }
}

可以看到上面的兩個更新是可以獨立拆分出來的,分成兩個小的reducer封断,最后合成出一個完整的state

function reducer(state=state,action){
   return {
       todos:todos(state.todos,action),
       visibilityFilter:visibilityFilter(state.visibilityFilter,action)
   }
}
function todos(state=[],action) {
   switch(action.type){
       case 'ADD_TODO':
         return [
            ...state,
            {
                text:action.text,
                completed:false
            }
         ]
        default:
          return state 
   }
}
function visibilityFilter(state='SHOW_ALL',action){
    switch(action.type){
        case 'SET_VISIBILITY_FILTER':
          return action.filter
        default:
          return state  
    }
}

redux本身提供了一個方法combineReducers(reducers)斯辰,輔助我們根據給出的key值讓拆分出來的函數管理對應的state,返回一個完整的reducer坡疼,合并后的 reducer 可以調用各個子 reducer彬呻,并把它們返回的結果合并成一個 state 對象。

 const reducer = combineReducers({
  todos,
  visibilityFilter
})

下面是combineReducers部分源碼

return function combination(state = {}, action) {
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }

通過上面的源碼可以看到柄瑰,在調用combineReducers方法后返回一個函數combination闸氮,這個combination就作為接下來要說的createStore的第一個必填參數傳入,生成store

createStore

cosnt store = createStore(reducer);

createStore接收三個參數教沾,reducers蒲跨、initState、enhancer(中間件)
createStore最重要的是在內部構造了四個方法授翻,分別是:
dispatch: 接收action或悲,更新狀態(tài)數。
getState: 得到當前最新state樹堪唐。
replaceReducer: 替換reducers巡语,初始化state樹。
subscribe: 用于觀察者模式淮菠,設置監(jiān)聽男公,并返回退出監(jiān)聽方法,在調用dispatch時兜材,運行所有被監(jiān)聽對象理澎。

createStore接受三個參數,第一參數reducers是必填曙寡,第二個參數initState用來初始化state糠爬,第三個參數用來合并中間件,這里我們分析一下store的四個方法和中間件举庶,initSate初始化狀態(tài)和服務端渲染沒有做過多研究执隧,之后補充。

createStore創(chuàng)建一個單一的store,用來管理唯一state,state是一個對象,store管理這個對象镀琉,通過store.getState返回最新的state峦嗤。

function getState() {
    return currentState;
}

redux規(guī)定不能直接修改應用的狀態(tài) state,想要修改state只能通過dispatch方法,dispatch接受一個叫action的對象屋摔,里面必須包含一個 type 字段來聲明你到底想干什么烁设。redux相當于生成了一個共享,共享的狀態(tài)如果可以被任意修改的話钓试,那么程序的行為將非常不可預料装黑,所以我們提高了修改數據的門檻:你必須通過 dispatch 執(zhí)行某些允許的修改操作,而且必須大張旗鼓的在 action 里面聲明弓熏。

/**
*通過部分源碼可以看到dispatch接受action,然后調用reducers函數放回新的state更新當前的state
*至于怎么寫這個reducers有很多方式恋谭,不做本文重點,不做介紹挽鞠,可以看官方文檔疚颊,至于用哪種方式,怎么用就看個人喜好信认,用與不用它就在那里不悲不喜
*/
const dispatch = (action)=>{
    currentState = reducers(currentState,action);
}

現在可以獲取到state并修改它材义,那我們想每次更新狀態(tài)的時候做一些統(tǒng)一的操作,比如打印出state看到變化狮杨,我們需要調用store.subscribe

nextListeners.push(listener);

return function unsubscribe() {
    var index = nextListeners.indexOf(listener);
    nextListeners.splice(index, 1);
};

通過部分源碼我們可以看到母截,subscribe其實就是接收一個函數,然后把它存在一個數組橄教,每次dispatch調用時遍歷一遍這個數組

//dispatch部分源碼
for (var i = 0; i < listeners.length; i++) {
  listeners[i]();
}

還有一個replaceReducer方法清寇,他接受一個新的reducers替換舊的reducers,然后初始化一個新的state

function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer         // 就是這么耿直簡單粗暴护蝶!
    dispatch({ type: ActionTypes.INIT }) // 觸發(fā)生成新的 state 樹
  }

dispatch與middleware

store里的dispatch函數只能接收一個action然后傳給reducer更新state然后重新渲染华烟,想要擴展一些操作就需要中間件來加強dispatch的功能,例如向服務器請求后進行后再進行更新state的操作持灰,可自己定義一個中間件來進行想要的強化盔夜,輸出不夠補輸出,防御不夠補護甲堤魁。

applyMiddleware(...middleware)

applyMiddleware方法接收多個中間件方法喂链,將多個中間方法合成一個新的dispatch,然后執(zhí)行新的dispatch時根據傳入順序逐個執(zhí)行

var store = createStore(reducer, preloadedState, enhancer)
      
      var dispatch = store.dispatch // 指向原 dispatch
      var chain = [] // 存儲中間件的數組
 
      var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
      }
 
      chain = middlewares.map(middleware => middleware(middlewareAPI))  //生成一個中間的數組
  
      dispatch = compose(...chain)(store.dispatch)

      return {
        ...store, // store 的 API 中保留 getState / subsribe / replaceReducer
        dispatch  // 新 dispatch 覆蓋原 dispatch妥泉,往后調用 dispatch 就會觸發(fā) chain 內的中間件鏈式串聯執(zhí)行
      }

在上面代碼中最主要的就是compose函數椭微,它將個所有的中間件合并然后生成新的增強后dispatch,就像dispatch加了一層又一層的buff,為什么是說它是一層一層的是因為compose將所有的中間件組合生成新的dispatch就是洋蔥一樣將dispatch一層層包裹傳遞

function compose(...funcs) {
  return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}

我們調用compose函數將chain里的函數從右向左依次執(zhí)行,傳入的arg就是store.dispatch,假設我們添加了三個中間件最后生成的dispatch

dispatch = f1(f2(f3(store.dispatch))))

就像洋蔥一樣將dispatch一層層包裹,首先執(zhí)行f3然后將f3中間件的返回值是一個函數作為參數傳給傳給f2境氢,f2執(zhí)行后又包裹了一層f2的返回值锐借,然后傳給f1,最后f1將自己的返回值與之前兩個中間件的返回值組一起生成新的dispatch函數

我們用一個簡單的logger中間件理解一下這個過程

const logger = (store)=>(next)=>(action)=>{
    console.log(store.getState());
    next(action);
    console.log(store.getState());
}

之前在applyMiddleware有這樣一行代碼

chain = middlewares.map(middleware => middleware(middlewareAPI)) 

它生出一個中間件的數組集合的同事其實也執(zhí)行了每個中間件,所以中間函數到下一層返回了一個函數

(next)=>(action)=>{
    console.log(store.getState());
    next(action);
    console.log(store.getState());
}

而到了compose合并的時候返回的的新函數就是

(action)=>{
    console.log(store.getState());
    next(action);
    console.log(store.getState());
}

理解這個過程很重要踏堡,這里執(zhí)行dispatch沒有直接調用store里的dispatch而是傳進去唱矛,是因為如果是多個中間件調用的話缺前,這里的next可能是上一層的(action)=>{}函數不是store.dispatch
我們在加一個logNext的函數將next打印出來看一下效果

const logNext = (store)=>(next)=>(action)=>{
    console.log(next.toString());
    next(action);
}

我們可以看到此時的next

function (n){console.log(e.getState()),t(n),console.log(e.getState())}

它的運行順序

             --------------------------------------
            |            middleware1              |
            |    ----------------------------     |
            |    |       middleware2         |    |
            |    |    -------------------    |    |
            |    |    |  middleware3    |    |    |
            |    |    |                 |    |    |
          next next next  ———————————   |    |    |
dispatch  —————————————> |  reducer |   |    |    |
nextState <————————————— |          |   |    |    |
            |    |    |  ———————————    |    |    |
            |    |    |                 |    |    |
            |    |    -------------------    |    |
            |    ----------------------------     |
            --------------------------------------

在生成新的dispatch是層層包裹锅尘。層層進入监氢,層層冒出,就像是剝洋蔥

總結

本文介紹了redux的一些機制藤违,它可以幫助我們管理狀態(tài)忙菠,并且通過準守它的規(guī)定,可以讓狀態(tài)的修改可預料纺弊,并且實現了自動渲染,redux是優(yōu)秀的函數式編程應用骡男,體現了函數式編程的靈活性淆游,可以根據自己的需求自定義擴展,來管理使用隔盛。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末犹菱,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子吮炕,更是在濱河造成了極大的恐慌腊脱,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件龙亲,死亡現場離奇詭異陕凹,居然都是意外死亡,警方通過查閱死者的電腦和手機鳄炉,發(fā)現死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門杜耙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拂盯,你說我怎么就攤上這事佑女。” “怎么了谈竿?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵团驱,是天一觀的道長。 經常有香客問我空凸,道長嚎花,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任劫恒,我火速辦了婚禮贩幻,結果婚禮上轿腺,老公的妹妹穿的比我還像新娘。我一直安慰自己丛楚,他們只是感情好族壳,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著趣些,像睡著了一般仿荆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坏平,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天拢操,我揣著相機與錄音,去河邊找鬼舶替。 笑死令境,一個胖子當著我的面吹牛,可吹牛的內容都是我干的顾瞪。 我是一名探鬼主播舔庶,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼陈醒!你這毒婦竟也來了惕橙?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤钉跷,失蹤者是張志新(化名)和其女友劉穎弥鹦,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體爷辙,經...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡彬坏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了膝晾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苍鲜。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖玷犹,靈堂內的尸體忽然破棺而出混滔,到底是詐尸還是另有隱情,我是刑警寧澤歹颓,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布坯屿,位于F島的核電站,受9級特大地震影響巍扛,放射性物質發(fā)生泄漏领跛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一撤奸、第九天 我趴在偏房一處隱蔽的房頂上張望吠昭。 院中可真熱鬧喊括,春花似錦、人聲如沸矢棚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒲肋。三九已至蘑拯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兜粘,已是汗流浹背申窘。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留孔轴,地道東北人剃法。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像路鹰,于是被迫代替她去往敵國和親玄窝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內容

  • 學習必備要點: 首先弄明白悍引,Redux在使用React開發(fā)應用時,起到什么作用——狀態(tài)集中管理 弄清楚Redux是...
    賀賀v5閱讀 8,896評論 10 58
  • 一帽氓、什么情況需要redux趣斤? 1、用戶的使用方式復雜 2黎休、不同身份的用戶有不同的使用方式(比如普通用戶和管...
    初晨的筆記閱讀 2,028評論 0 11
  • 前言 本文 有配套視頻浓领,可以酌情觀看。 文中內容因各人理解不同势腮,可能會有所偏差联贩,歡迎朋友們聯系我討論。 文中所有內...
    珍此良辰閱讀 11,904評論 23 111
  • 看到這篇文章build an image gallery using redux saga捎拯,覺得寫的不錯泪幌,長短也適...
    smartphp閱讀 6,154評論 1 29
  • 本文將開始詳細分析如何搭建一個React應用架構。 一. 前言 現在已經有很多腳手架工具署照,如create-reac...
    字節(jié)跳動技術團隊閱讀 4,325評論 1 23