關(guān)于Redux框架,Reducer中state處理方式的探討

前言



在react+redux項(xiàng)目里,關(guān)于reducer中處理state的方式,在redux官方文檔中有這樣一段描述 (鏈接):

不要修改 state暮的。 使用 Object.assign() 創(chuàng)建了一個(gè)副本。不能這樣使用 Object.assign(state, {visibilityFilter: action.filter }),因?yàn)樗鼤?huì)改變第一個(gè)參數(shù)的值桃犬。你必須把第一個(gè)參數(shù)設(shè)置為空對(duì)象恋拍。你也可以開啟對(duì)ES7提案對(duì)象展開運(yùn)算符的支持, 從而使用 { ...state, ...newState }達(dá)到相同的目的。

對(duì)此,我們可能會(huì)產(chǎn)生以下一些疑問:

  • 為什么要?jiǎng)?chuàng)建副本state?
  • 怎樣創(chuàng)建副本state才是合理的?
  • 外部插件直接更新state是否合理?

為什么要?jiǎng)?chuàng)建副本state



redux-devtools中,我們可以查看到redux下所有通過reducer更新state的記錄,每一個(gè)記錄都對(duì)應(yīng)著內(nèi)存中某一個(gè)具體的state,讓用戶可以追溯到每一次歷史操作產(chǎn)生與執(zhí)行時(shí),當(dāng)時(shí)的具體狀態(tài),這也是使用redux管理狀態(tài)的重要優(yōu)勢(shì)之一.

若不創(chuàng)建副本,redux的所有操作都將指向內(nèi)存中的同一個(gè)state,我們將無從獲取每一次操作前后,state的具體狀態(tài)與改變,若沒有副本,redux-devtools列表里所有的state都將被最后一次操作的結(jié)果所取代.我們將無法追溯state變更的歷史記錄.

創(chuàng)建副本也是為了保證向下傳入的this.props與nextProps能得到正確的值,以便我們能夠利用前后props的改變情況以決定如何render組件

怎樣創(chuàng)建副本state才是合理的?



既然創(chuàng)建副本是為了保留更改歷史,那么,原則上原state所有被改動(dòng)過的屬性都應(yīng)該被創(chuàng)建副本,

我們可以看一下官方示例(鏈接):

  function todoApp(state = initialState, action) {
    switch (action.type) {
      case SET_VISIBILITY_FILTER:
        return Object.assign({}, state, {
          visibilityFilter: action.filter
        })
      default:
        return state
    }
  }

示例中的state結(jié)構(gòu)較為簡(jiǎn)單,而實(shí)際項(xiàng)目中的業(yè)務(wù)需求可能遠(yuǎn)比示例中更為復(fù)雜.

若visibilityFilter是下面這樣的結(jié)構(gòu):

visibilityFilter:{
  a:{
    c:1
  },
  b:{
    d:2
  }
}

而我們需要改動(dòng)的是visibilityFilter.b.d,就會(huì)產(chǎn)生一些問題,方案可以是以下幾種:

方案1

將todoApp這個(gè)reducer拆分為更細(xì)化的reducer,以保證visibilityFilter屬性中嵌套對(duì)象b的屬性d能得到正確更新

方案2

采用官方實(shí)例中Object.assign方法,但需要將visibilityFilter中未更新的對(duì)象用原state的中的對(duì)象進(jìn)行手動(dòng)賦值

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: {
          state.visibilityFilter.a,
          b:{
            d:action.filter
          }
        }
      })
      default:
        return state
  }
}

或采用對(duì)象展開運(yùn)算符

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return {
        ...state, 
        visibilityFilter:{
          ...state.visibilityFilter, 
          b:{
            ...state.visibilityFilter.b,
            d:action.filter
          }
        }
      }
      default:
        return state
  }
}
方案3

將state進(jìn)行深度對(duì)象克隆后,再進(jìn)行更新,可以用原生js去實(shí)現(xiàn),但這里直接采用lodash的cloneDeep方法

import cloneDeep from 'lodash/cloneDeep'

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      const newState = cloneDeep(state)
      newState.visibilityFilter.b.d = action.filter
      return newState
    default:
      return state
  }
}
方案4

采用官方提供的Immutability Helper工具中update()方法進(jìn)行數(shù)據(jù)更新(鏈接)

import update from 'react/lib/update'

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return update(state, {
        visibilityFilter:{
          d:{$set: action.filter}
        }
      })
    default:
      return state
  }
}
方案小結(jié)
  • 方案1在結(jié)構(gòu)更復(fù)雜時(shí),將產(chǎn)生更多更為細(xì)化的reducer,而其中可能有很多reducer其實(shí)并沒有必要再進(jìn)行深層次的細(xì)化拆分.

  • 方案2中,我們需要將原對(duì)象中所有沒有變更的對(duì)象手動(dòng)賦值給副本對(duì)象,并確保副本對(duì)象的結(jié)構(gòu)完整性與原對(duì)象相同.相比方案1,方案2的優(yōu)勢(shì)則在于更少的代碼量.

  • 方案3是上述方案中最為簡(jiǎn)便且不易出錯(cuò)的方案,但深度復(fù)制,將會(huì)為整個(gè)被復(fù)制的對(duì)象創(chuàng)建一個(gè)完整的副本,與方案1,2中只創(chuàng)建變更部分的副本相比,性能上將消耗更多內(nèi)存,在執(zhí)行效率上也會(huì)明顯低于前面的方案.

  • 方案4不存在方案3的性能問題,并且相比方案2而言,創(chuàng)建副本的方式更為簡(jiǎn)單,所以本文更為推薦采用此方案創(chuàng)建副本

錯(cuò)誤示例!

由于官方示例采用Object.assign方法創(chuàng)建副本,所以有時(shí)候我們?yōu)榱藭鴮懞?jiǎn)便,可能會(huì)出現(xiàn)這樣的副本創(chuàng)建方式

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      const newState = Object.assign({}, state)
      newState.visibilityFilter.b.d = action.filter
      return newState  
    default:
      return state
  }
}

或者

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      state.visibilityFilter.b.d = action.filter
      return Object.assign({}, state)
    default:
      return state
  }
}

此處我們對(duì)Object.assign方法進(jìn)行一個(gè)小的測(cè)試

    const x = {
      a1: {a2: 1},
      b1: {
        b2: {b4: 2},
        b3: {b5: 3}
      },
      c1:4
    }
    const y = Object.assign({}, x)
    y.b1.b3.b5 = 8
    y.c1 = 9
    console.log(x); //=> {a1:{a2:1}, b1:{b2:{b4:2}, b3:{b5:8}}, c1:4}
    console.log(y); //=> {a1:{a2:1}, b1:{b2:{b4:2}, b3:{b5:8}}, c1:9}
    console.log(x == y); //=> false
    console.log(x.a1 == y.a1); //=> true
    console.log(x.b1 == y.b1); //=> true
    console.log(x.b1.b2 == y.b1.b2); //=> true
    console.log(x.b1.b3 == y.b1.b3); //=> true
    console.log(x.b1.b3.b5 == y.b1.b3.b5); //=> true

由此可見Object.assign操作后,x,y的區(qū)別是其自身的引用地址和屬性c1所引用的數(shù)值不同,而屬性a1,b1所引用的對(duì)象及其內(nèi)部子對(duì)象在內(nèi)存中是同一個(gè)引用地址,這就意味著,改變y.b1.b3.b5的值實(shí)際上同時(shí)也改變了x.b1.b3.b5的值,

這會(huì)導(dǎo)致redux中state歷史混亂以及之后components所調(diào)用的this.props與nextProps無法得到正確的值

外部插件直接更新state是否合理?



筆者目前接觸較多的是redux-form插件,所以,此處暫且以redux-form更新state的方式進(jìn)行一些探討.

redux-form

當(dāng)組件采用redux-form進(jìn)行監(jiān)聽后,其內(nèi)部form表單里的對(duì)象都將被放入redux的state中進(jìn)行管理,并由redux-form自身發(fā)起action進(jìn)行更新刪除等操作.

而問題在于,redux-form會(huì)為每一次的表單更新都發(fā)起一次action,也就意味著我們?cè)谝粋€(gè)input框里輸入一句簡(jiǎn)單的"hello world",將會(huì)有11個(gè)state副本產(chǎn)生

首先,就創(chuàng)建副本而言,其本身是一種性能消耗,而redux創(chuàng)建副本的目的是為了追溯歷史操作與更改,所以我們應(yīng)該考慮,類似redux-form這樣短時(shí)間高頻率的更改state的方式,產(chǎn)生大量細(xì)碎的輸入歷史,我們是否應(yīng)該避免這樣的更新方式?

其次,若外部插件直接更新state,由于其處理方式大多封裝在其內(nèi)部,若插件自身對(duì)創(chuàng)建state副本的方式?jīng)]有深入的考慮,其高頻率的更新state,可能會(huì)對(duì)整個(gè)項(xiàng)目的運(yùn)行效率產(chǎn)生較為嚴(yán)重的影響.

小結(jié)

就redux-form使用體驗(yàn)而言,在一些輸入場(chǎng)景中,其會(huì)導(dǎo)致輸入操作產(chǎn)生明顯的頓挫感,可以猜測(cè)這是由于其工作方式導(dǎo)致的性能問題造成的結(jié)果.

所以,外部插件直接更新state可能會(huì)使一些業(yè)務(wù)狀態(tài)更方便于管理,但其對(duì)整個(gè)項(xiàng)目的性能影響情況,可能需要我們?nèi)ド髦卦u(píng)估.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末提针,一起剝皮案震驚了整個(gè)濱河市命爬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辐脖,老刑警劉巖遇骑,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異揖曾,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)亥啦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門炭剪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人翔脱,你說我怎么就攤上這事奴拦。” “怎么了届吁?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵错妖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我疚沐,道長(zhǎng)暂氯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任亮蛔,我火速辦了婚禮痴施,結(jié)果婚禮上究流,老公的妹妹穿的比我還像新娘辣吃。我一直安慰自己,他們只是感情好芬探,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布神得。 她就那樣靜靜地躺著,像睡著了一般偷仿。 火紅的嫁衣襯著肌膚如雪哩簿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天炎疆,我揣著相機(jī)與錄音卡骂,去河邊找鬼。 笑死形入,一個(gè)胖子當(dāng)著我的面吹牛全跨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播亿遂,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼浓若,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼渺杉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挪钓,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤是越,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后碌上,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體倚评,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年馏予,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了天梧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡霞丧,死狀恐怖呢岗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛹尝,我是刑警寧澤后豫,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站突那,受9級(jí)特大地震影響挫酿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陨收,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一饭豹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧务漩,春花似錦拄衰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至居触,卻和暖如春妖混,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轮洋。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工制市, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人弊予。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓祥楣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子误褪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容