對使用Redux和Redux-saga管理狀態的思考

原文地址在個人博客, 轉載請註明出處,謝謝!javascript

概述

本文介紹了對 Redux 狀態管理的思想、原理、架構方法的認識和思考以及配合redux-saga處理異步操做的實踐java

前言

You know, React 只是屬於MV*架構模式的 view 層,是一種狀態機,只使用 React 難以控制大型、複雜的應用,它須要一些框架來幫助管理狀態,所以如何有效、簡單、易於測試地管理這個狀態機是各類架構框架感興趣的。Facebook 早就意識到這個問題並提出了 Flux 架構,比較複雜; 後來出現了 Redux、Mobx 等。MobX 能夠處理簡單數據流的場景,能夠實現精確更新; Redux 是從 Flux 和其餘框架借鑑了一些思想, 它比 Flux 簡單、易於理解、用於處理複雜數據流,並具備很強的擴展性,社區誕生了像redux-thunk、redux-promise、redux-saga等中間件用於方便地處理異步操做。最近也在項目中使用了 Redux 及其中間件 redux-saga 來管理狀態和處理異步操做,這篇文章就來談談我對它們的思考和實踐。node

正文

Redux 思想

先來談談背景(需求)react

我以爲理解Redux的思想,談談MV*架構模式的思路也許會有幫助。redux

MV*架構模式,它們的核心都是職責分離、解耦,不一樣的層次作不一樣的事,能讓一個複雜、混亂的應用變得思路清晰,代碼能夠複用,而且易於測試,有利於分工合做,構建更大、更復雜的應用。promise

一個應用要包括哪些功能?表現(view)、處理數據的邏輯(model)以及數據映射到表現層的邏輯(presenter or controller),數據在這三層之間流通(MVVM模式經過數據雙向綁定實現view和model同步)。瀏覽器

React 根據state來render,它只是個狀態機,並無解決管理狀態的問題。咱們在單純的使用React來寫組件的時候,常常會遇到組件間通訊和管理組件state的問題,前者經常使用的解決辦法就是把數據提到父組件共享;後者管理state簡單的狀況還行,一複雜就很麻煩且容易出錯,再遇到一些須要異步處理的操做,想一想就頭皮發麻。react-router

當你開發中遇到一些反人類的操做時,試着去想如何改變一下思路讓它變得更簡單,別耐着性子安慰本身開發就是這樣 :)架構

解決方案框架

Redux 正是用來解決大型React應用所面臨的狀態管理、數據流通、異步處理、測試、團隊合做等問題:

Redux 用單一的object tree來表示整個應用的state,這個表示state的對象樹被放在惟一的store 中,state至關於store的快照;全部組件都會經過API拿到這個state,各取所需;

Redux 把頁面上用戶的操做或者瀏覽器的行爲(如路由的變化)定義爲一個要更新state的action,這個action是一個普通對象,它包含了要執行動做的類別和傳遞到state的數據(若是有的話),它只代表要更改state的意圖,至關於一個信號,並不能直接修改state,Redux會集中處理這些信號,這個action由你來決定什麼時候發起;

定義好信號,你還須要根據不一樣的信號定義不一樣的邏輯函數(reducers)來更新state。

經過這張圖來整理一下:

「redux 原理圖」

咳咳...好比用戶點擊的一個按鈕,你在按鈕上綁定的回調函數調用了一個(多個)action creator,action creator就返回了一個更新state局部數據的action,store會根據這個(多個)action找到對應的reducers(reducer須要作拆分),按照action發起的順序依次執行來更新state,每一個reducer只負責更新本身關心那部分,根 reducer 把多個子 reducer 輸出合併成一個單一的 state 樹,生成一個新的state保存在store中,store中的state能夠經過相應API傳遞到子組件。

這就是整個數據流。

那Redux如何處理異步操做?

Redux借鑑了中間件思想,利用可擴展的中間件來改造dispatch函數。好比redux-thunk讓dispatch不只僅能夠接收action,還能夠接受函數做爲參數,你能夠在這個函數裏完成異步操做。再如redux-saga更強大、也更復雜,在後面會講到。

Redux 架構方法

對於React技術棧,Redux實現了react-redux庫來讓Redux管理React應用(其餘框架也有相應的庫),裏面集成了一些有用的函數來把一些明確的流程自動化,如createStore用於建立惟一store,能夠把根reducer傳進createStore使store自動調用對應reducer,能夠擴展中間件;提供<Provider store>組件和connect高階組件用來包裹render component並傳遞state,connect還能自動dispatch,讓你只要調用action creator就能dispatch;提供combineReducers來組合分割的reducers等。

知道這些特性,就能夠配合react-router構建大型應用了:

總的思路就是:利用react-router 把應用分割爲各個頁面,reducer、action creator也跟隨頁面分割而分割。每一個路由對應的頁面下都有components和containers,分別存放functional components 和class components,前者用來渲染,後者當作containers被connect包裹,containers包裹components;containers從connect獲得state並映射須要的數據到子組件的props,子組件再向下傳遞。

具體如何構建React + Redux + react-router,我在另外一篇博客裏講了。

使用這種架構,開發大型應用變得駕輕就熟。

Redux 存在的問題

可是當我深刻項目開發的時候,也逐漸發現了一些問題:

  • 這種架構項目結構不夠扁平化,文件嵌套比較深,思路比較複雜,搭建、寫起來比較麻煩,上手有難度;
  • 因爲全部action creator都定義在頁面層次上,讓子組件調用必須一層一層的傳遞,很麻煩且很是容易出錯,也很難調試;
  • state難以作到局部更新(這個能夠用reselect
  • Redux只是傳遞了一種思路,定義了幾個簡單的API,很靈活,架構方式不固定,設計方式不固定(如:如何設計state樹)但這也是它的缺點,新人每每看完一遍仍是不知道怎麼作,對新人不友好

總之,redux能夠勝任複雜數據流的應用,可是也比較難,前期架構比較麻煩,適合有經驗的人。

使用redux-saga處理異步操做

Redux 倡導action 和reducer儘量"純淨",沒有什麼「反作用」。但是像一些異步操做好比獲取數據是必須的,在哪處理這些反作用呢?redux 把這些"不純淨的"任務交給了中間件,經過 向createStore裏應用中間件,在交由store處理action以前就能夠對其完成一些其餘的操做:

「redux 中間件」

而redux-saga 是Redux一個強大但並不複雜的用於異步處理的中間件。

它的思路是什麼?相比其餘redux異步中間件如redux-thunk、redux-promise有什麼不一樣?

先看名字來理解:saga,這個術語經常使用於CQRS架構,表明查詢與責任分離

沒錯,就是查詢(dispatch)與責任(sagas)分離。saga提供了action監聽函數,只需在組件裏dispatch 相應type的action,就能夠自動調用你定義好的對應這個action的異步處理函數(sagas)來完成任務,保證了只在組件裏dispatch action來發起異步操做而不是redux-thunk、redux-promise的調用action creators。

另一大特點就是redux-saga作到了異步代碼以同步方式寫,很是直觀方便,怎麼作到的呢?它是利用了ES6新魔法Generator迭代器,能夠完美解決異步回調地獄,讓你以同步方式寫異步。saga正是利用Generator特性讓其處理異步變得很是方便又容易理解。這是一個常見的請求後臺數據的異步操做,感覺一下:

function *fetchNodeDetailByNodeId({ payload: { nodeId } }, { call, put }) {
      try {
        const { data, status }= yield call(fetchNodeDetailByNodeId, nodeId)
        if (data && status.errmsg === 'success') {
          yield put({
            type: 'setStates',
            payload: {
              nodeDetailData: data,
            },
          });
        } else {
          message.info('開了個小差,再試一次吧..');
        }
      } catch (error) {
        console.log(error);
      }
    },

call 和 put 是saga的API,至關於dispatch,可是並非真正執行dispatch,只是發送你指定的指令,交由saga中間件來執行這個指令。這樣看來,這個saga函數就是一些指令的集合,稱爲effects,反作用,用來描述任務

爲啥要描述指令而不直接調用呢?這樣是由於易於測試,若是直接調用,你還得模擬調用的函數,詳見redux-saga文檔。

我以爲redux-saga相比於其餘中間件的優勢:

  • 查詢與責任分離,保證了action的純潔性,符合redux設計思想
  • 實現以同步方式寫異步操做,容易理解,邏輯清晰
  • 經過發送指令而不是直接調用讓異步操做變得容易測試
  • 監聽、執行自動化
  • 提供了豐富強大的指令來完成複雜的操做,好比無阻塞調用,同時執行多個任務等

講道理,任何redux異步操做均可以讓saga這個中間件來完成,很是複雜的一樣能夠勝任,而且很容易理解(異步操做以同步方式寫)和測試。再配合dva,能夠減輕redux的複雜度同時完成更強大的功能。

這樣以來,redux配合saga,就可讓它們各司其職,整個思路也變得清晰起來:

redux 倡導action和reducer要純潔,那就讓全部異步操做這些不純潔的任務交給saga,reducer不用變,仍是純函數;定義好對應action的sagas專門用來處理異步操做,我只要在組件須要的地方里dispatch 純action就好了,符合redux設計思想。

總結

使用redux來管理應用狀態適用於複雜的應用,而複雜的應用會有複雜的異步處理,異步處理不要用redux的action creator,它不是用來作這個的,也違背了redux設計思想,redux把這些任務交給了異步中間件,應該由它們來完成。使用redux saga是一個推薦的選擇,它懂redux,也懂你須要什麼。另外,既然你用到了saga,不妨試試dva架構,5分鐘上手,值得一試。

相關文章
相關標籤/搜索