[React-10] 狀態(tài)管理

傳統(tǒng)MVC框架的缺陷

什么是MVC裕偿?

mvc

MVC的全名是Model View Controller引润,是模型(model)-視圖(view)-控制器(controller)的縮寫,是一種軟件設計典范栈幸。

V即View視圖是指用戶看到并與之交互的界面愤估。

M即Model模型是管理數(shù)據(jù) ,很多業(yè)務邏輯都在模型中完成侦镇。在MVC的三個部件中灵疮,模型擁有最多的處理任務。

C即Controller控制器是指控制器接受用戶的輸入并調(diào)用模型和視圖去完成用戶的需求壳繁,控制器本身不輸出任何東西和做任何處理震捣。它只是接收請求并決定調(diào)用哪個模型構件去處理請求,然后再確定用哪個視圖來顯示返回的數(shù)據(jù)闹炉。

MVC只是看起來很美

MVC框架的數(shù)據(jù)流很理想蒿赢,請求先到Controller, 由Controller調(diào)用Model中的數(shù)據(jù)交給View進行渲染,但是在實際的項目中渣触,又是允許Model和View直接通信的羡棵。然后就出現(xiàn)了這樣的結果:


mvc的缺陷

Flux

在2013年,F(xiàn)acebook讓React亮相的同時推出了Flux框架嗅钻,React的初衷實際上是用來替代jQuery的皂冰,Flux實際上就可以用來替代Backbone.jsEmber.js等一系列MVC架構的前端JS框架养篓。

其實FluxReact里的應用就類似于Vue中的Vuex的作用秃流,但是在Vue中,Vue是完整的mvvm框架柳弄,而Vuex只是一個全局的插件舶胀。

React只是一個MVC中的V(視圖層),只管頁面中的渲染碧注,一旦有數(shù)據(jù)管理的時候嚣伐,React本身的能力就不足以支撐復雜組件結構的項目,在傳統(tǒng)的MVC中萍丐,就需要用到Model和Controller轩端。Facebook對于當時世面上的MVC框架并不滿意,于是就有了Flux, 但Flux并不是一個MVC框架逝变,他是一種新的思想船万。

flux

  • View: 視圖層

  • ActionCreator(動作創(chuàng)造者):視圖層發(fā)出的消息(比如mouseClick)

  • Dispatcher(派發(fā)器):用來接收Actions、執(zhí)行回調(diào)函數(shù)

  • Store(數(shù)據(jù)層):用來存放應用的狀態(tài)骨田,一旦發(fā)生變動耿导,就提醒Views要更新頁面

Flux的流程:

  1. 組件獲取到store中保存的數(shù)據(jù)掛載在自己的狀態(tài)上

  2. 用戶產(chǎn)生了操作,調(diào)用actions的方法

  3. actions接收到了用戶的操作态贤,進行一系列的邏輯代碼舱呻、異步操作

  4. 然后actions會創(chuàng)建出對應的action,action帶有標識性的屬性

  5. actions調(diào)用dispatcher的dispatch方法將action傳遞給dispatcher

  6. dispatcher接收到action并根據(jù)標識信息判斷之后,調(diào)用store的更改數(shù)據(jù)的方法

  7. store的方法被調(diào)用后箱吕,更改狀態(tài)芥驳,并觸發(fā)自己的某一個事件

  8. store更改狀態(tài)后事件被觸發(fā),該事件的處理程序會通知view去獲取最新的數(shù)據(jù)

Redux

React 只是 DOM 的一個抽象層茬高,并不是 Web 應用的完整解決方案兆旬。有兩個方面,它沒涉及怎栽。

  • 代碼結構

  • 組件之間的通信

2013年 Facebook 提出了 Flux 架構的思想丽猬,引發(fā)了很多的實現(xiàn)德谅。2015年凯正,Redux 出現(xiàn),將 Flux 與函數(shù)式編程結合一起凄诞,很短時間內(nèi)就成為了最熱門的前端架構强饮。

如果你不知道是否需要 Redux由桌,那就是不需要它

只有遇到 React 實在解決不了的問題,你才需要 Redux

簡單說邮丰,如果你的UI層非常簡單行您,沒有很多互動,Redux 就是不必要的剪廉,用了反而增加復雜性邑雅。

  • 用戶的使用方式非常簡單

  • 用戶之間沒有協(xié)作

  • 不需要與服務器大量交互,也沒有使用 WebSocket

  • 視圖層(View)只從單一來源獲取數(shù)據(jù)

需要使用Redux的項目:

  • 用戶的使用方式復雜

  • 不同身份的用戶有不同的使用方式(比如普通用戶和管理員)

  • 多個用戶之間可以協(xié)作

  • 與服務器大量交互妈经,或者使用了WebSocket

  • View要從多個來源獲取數(shù)據(jù)

從組件層面考慮,什么樣子的需要Redux:

  • 某個組件的狀態(tài)捧书,需要共享

  • 某個狀態(tài)需要在任何地方都可以拿到

  • 一個組件需要改變?nèi)譅顟B(tài)

  • 一個組件需要改變另一個組件的狀態(tài)

Redux的設計思想:

  1. Web 應用是一個狀態(tài)機吹泡,視圖與狀態(tài)是一一對應的。

  2. 所有的狀態(tài)经瓷,保存在一個對象里面(唯一數(shù)據(jù)源)爆哑。

注意:flux、redux都不是必須和react搭配使用的舆吮,因為flux和redux是完整的架構揭朝,在學習react的時候,只是將react的組件作為redux中的視圖層去使用了色冀。

Redux的使用的三大原則:

  • Single Source of Truth(唯一的數(shù)據(jù)源)

  • State is read-only(狀態(tài)是只讀的)

  • Changes are made with pure function(數(shù)據(jù)的改變必須通過純函數(shù)完成)

自己實現(xiàn)Redux

不使用react潭袱,直接使用原生的html/js來寫一個簡易的的redux

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Redux principle 04</title>
</head>
<body>
  <h1>redux principle</h1>
  <div class="counter">
    <span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
    <span class="count" id="count"></span>
    <span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
  </div>
  <script>
    // 定義一個方法,用于集中管理state和dispatch, changeState改名了锋恬,專業(yè)的叫法是reducer
    const createStore = (reducer) => {
      // 定義一個初始的state
      let state = null
      // getState用于獲取狀態(tài)
      const getState = () => state
      
      // 定義一個監(jiān)聽器屯换,用于管理一些方法
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)

      // 定義一個dispatch方法,讓每次有action傳入的時候返回reducer執(zhí)行之后的結果
      const dispatch = (action) => {
        // 調(diào)用reducer來處理數(shù)據(jù)
        state = reducer(state, action)
        // 讓監(jiān)聽器里的所有方法運行
        listeners.forEach(listener => listener())
      }
      //  初始化state
      dispatch({})
      return {
        getState,
        dispatch,
        subscribe
      }
    }
    // 定義一個計數(shù)器的狀態(tài)
    const countState = {
      count: 10
    }
    // 定一個方法叫changeState,用于處理state的數(shù)據(jù)彤悔,每次都返回一個新的狀態(tài)
    const changeState = (state, action) => {
      // 如果state是null, 就返回countState
      if (!state) return countState
      switch(action.type) {
        // 處理減
        case 'COUNT_DECREMENT':
          return {
            ...state,
            count: state.count - action.number
          }
        // 處理加        
        case 'COUNT_INCREMENT':
          return {
            ...state,
            count: state.count + action.number
          }
        default:
          return state
      }
    }

    // 創(chuàng)建一個store
    const store = createStore(changeState)
    // 定義一個方法用于渲染計數(shù)器的dom
    const renderCount = () => {
      const countDom = document.querySelector('#count')
      countDom.innerHTML = store.getState().count
    }
    // 初次渲染數(shù)據(jù)
    renderCount()
    // 監(jiān)聽嘉抓,只要有dispatch,renderCount就會自動運行
    store.subscribe(renderCount)
  </script>
</body>
</html>

使用Redux框架

Redux的流程:

redux

1.store通過reducer創(chuàng)建了初始狀態(tài)

2.view通過store.getState()獲取到了store中保存的state掛載在了自己的狀態(tài)上

3.用戶產(chǎn)生了操作晕窑,調(diào)用了actions 的方法

4.actions的方法被調(diào)用抑片,創(chuàng)建了帶有標示性信息的action

5.actions將action通過調(diào)用store.dispatch方法發(fā)送到了reducer中

6.reducer接收到action并根據(jù)標識信息判斷之后返回了新的state

7.store的state被reducer更改為新state的時候,store.subscribe方法里的回調(diào)函數(shù)會執(zhí)行杨赤,此時就可以通知view去重新獲取state

Reducer必須是一個純函數(shù):

Reducer 函數(shù)最重要的特征是敞斋,它是一個純函數(shù)。也就是說望拖,只要是同樣的輸入渺尘,必定得到同樣的輸出。Reducer不是只有Redux里才有说敏,數(shù)組方法reduce, 它的第一個參數(shù)就是一個reducer

純函數(shù)是函數(shù)式編程的概念鸥跟,必須遵守以下一些約束。

  • 不得改寫參數(shù)

  • 不能調(diào)用系統(tǒng) I/O 的API

  • 不能調(diào)用Date.now()或者Math.random()等不純的方法盔沫,因為每次會得到不一樣的結果

由于 Reducer 是純函數(shù)医咨,就可以保證同樣的State,必定得到同樣的 View架诞。但也正因為這一點拟淮,Reducer 函數(shù)里面不能改變 State,必須返回一個全新的對象谴忧,請參考下面的寫法很泊。

// State 是一個對象
function reducer(state = defaultState, action) {
  return Object.assign({}, state, { thingToChange });
  // 或者
  return { ...state, ...newState };
}

// State 是一個數(shù)組
function reducer(state = defaultState, action) {
  return [...state, newItem];
}

關于action/reducer/store的更多概念,請查看官網(wǎng)

注意:

  1. 分離reducer的時候沾谓,每一個reducer維護的狀態(tài)都應該不同
  1. 通過store.getState獲取到的數(shù)據(jù)也是會按照reducers去劃分的
  1. 劃分多個reducer的時候委造,默認狀態(tài)只能創(chuàng)建在reducer中,因為劃分reducer的目的均驶,就是為了讓每一個reducer都去獨立管理一部分狀態(tài)

因為一個應用中只能有一個大的state昏兆,這樣的話reducer中的代碼將會特別特別的多,那么就可以使用combineReducers方法將已經(jīng)分開的reducer合并到一起

劃分reducer:

我們可以通過在createStore中傳入第二個參數(shù)來設置默認的state妇穴,但是這種形式只適合于只有一個reducer的時候爬虱。

最好把 State 對象設成只讀。要得到新的 State腾它,唯一辦法就是生成一個新對象跑筝。這樣的好處是,任何時候瞒滴,與某個 View 對應的 State 總是一個不變(immutable)的對象继蜡。
Redux異步**

通常情況下,action只是一個對象,不能包含異步操作稀并,這導致了很多創(chuàng)建action的邏輯只能寫在組件中仅颇,代碼量較多也不便于復用,同時對該部分代碼測試的時候也比較困難碘举,組件的業(yè)務邏輯也不清晰忘瓦,使用中間件了之后,可以通過actionCreator異步編寫action引颈,這樣代碼就會拆分到actionCreator中耕皮,可維護性大大提高,可以方便于測試蝙场、復用凌停,同時actionCreator還集成了異步操作中不同的action派發(fā)機制,減少編碼過程中的代碼量

常見的異步庫:

  • Redux-thunk(就用這個)
  • Redux-saga
  • Redux-effects
  • Redux-side-effects
  • Redux-loop
  • Redux-observable

基于Promise的異步庫:

  • Redux-promise
  • Redux-promises
  • Redux-simple-promise
  • Redux-promise-middleware

容器組件(Smart/Container Components)和展示組件(Dumb/Presentational Components)

展示組件 容器組件
作用 描述如何展現(xiàn)(骨架售滤、樣式) 描述如何運行(數(shù)據(jù)獲取罚拟、狀態(tài)更新)
直接使用 Redux
數(shù)據(jù)來源 props 監(jiān)聽 Redux state
數(shù)據(jù)修改 從 props 調(diào)用回調(diào)函數(shù) 向 Redux 派發(fā) actions
調(diào)用方式 手動 通常由 React Redux 生成

使用react-redux

可以先結合context來手動連接react和redux。

react-redux提供兩個核心的api:

  • Provider: 提供store
  • connect: 用于連接容器組件和展示組件
  1. Provider

    根據(jù)單一store原則 完箩,一般只會出現(xiàn)在整個應用程序的最頂層赐俗。

  2. connect

    語法格式為

    connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)

    一般來說只會用到前面兩個,它的作用是:

    • store.getState()的狀態(tài)轉化為展示組件的props
    • actionCreators轉化為展示組件props上的方法

特別強調(diào):

官網(wǎng)上的第二個參數(shù)為mapDispatchToProps, 實際上就是actionCreators

只要上層中有Provider組件并且提供了store, 那么弊知,子孫級別的任何組件阻逮,要想使用store里的狀態(tài),都可以通過connect方法進行連接秩彤。如果只是想連接actionCreators叔扼,可以第一個參數(shù)傳遞為null

Mobx

作為了解的內(nèi)容,在項目中使用redux的情況更多漫雷。

Mobx是一個功能強大瓜富,上手非常容易的狀態(tài)管理工具。redux的作者也曾經(jīng)向大家推薦過它珊拼,在不少情況下可以使用Mobx來替代掉redux。


mobx-flow.png

這張圖來自于官網(wǎng)流炕,把這張圖理解清楚了澎现。基本上對于mobx的理解就算入門了每辟。

官網(wǎng)有明確的核心概念使用方法剑辫,并配有egghead的視頻教程。這里就不一一贅述了渠欺。

要特別注意當使用 mobx-react 時可以定義一個新的生命周期鉤子函數(shù) componentWillReact妹蔽。當組件因為它觀察的數(shù)據(jù)發(fā)生了改變,它會安排重新渲染,這個時候 componentWillReact 會被觸發(fā)胳岂。這使得它很容易追溯渲染并找到導致渲染的操作(action)编整。

  • componentWillReact 不接收參數(shù)

  • componentWillReact 初始化渲染前不會觸發(fā) (使用 componentWillMount 替代)

  • componentWillReact 對于 mobx-react@4+, 當接收新的 props 時并在 setState 調(diào)用后會觸發(fā)此鉤子

  • 要觸發(fā)componentWillReact必須在render里面用到被觀察的變量

  • 使用Mobx之后不會觸發(fā)componentWillReceiveProps

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市乳丰,隨后出現(xiàn)的幾起案子掌测,更是在濱河造成了極大的恐慌,老刑警劉巖产园,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汞斧,死亡現(xiàn)場離奇詭異,居然都是意外死亡什燕,警方通過查閱死者的電腦和手機粘勒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屎即,“玉大人庙睡,你說我怎么就攤上這事〗9矗” “怎么了埃撵?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虽另。 經(jīng)常有香客問我暂刘,道長,這世上最難降的妖魔是什么捂刺? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任谣拣,我火速辦了婚禮,結果婚禮上族展,老公的妹妹穿的比我還像新娘森缠。我一直安慰自己,他們只是感情好仪缸,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布贵涵。 她就那樣靜靜地躺著,像睡著了一般恰画。 火紅的嫁衣襯著肌膚如雪宾茂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天拴还,我揣著相機與錄音跨晴,去河邊找鬼。 笑死片林,一個胖子當著我的面吹牛端盆,可吹牛的內(nèi)容都是我干的怀骤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼焕妙,長吁一口氣:“原來是場噩夢啊……” “哼蒋伦!你這毒婦竟也來了?” 一聲冷哼從身側響起访敌,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤凉敲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后寺旺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爷抓,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年阻塑,在試婚紗的時候發(fā)現(xiàn)自己被綠了蓝撇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡陈莽,死狀恐怖渤昌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情走搁,我是刑警寧澤独柑,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站私植,受9級特大地震影響忌栅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜曲稼,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一索绪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贫悄,春花似錦瑞驱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鸭津,卻和暖如春彤侍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背曙博。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工拥刻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怜瞒,地道東北人父泳。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓般哼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惠窄。 傳聞我的和親對象是個殘疾皇子蒸眠,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355