一文總結redux、react-redux、redux-saga

redux、react-redux、redux-saga總結

前言

hello你們好,我是風不識途,最近一直在整理 redux系列文章,發現對於初學者不太友好,關係錯綜複雜,難卻是不太難,就是比較複雜 (其實寫比較少),因此這篇帶你全面瞭解 redux、react-redux、redux-thunk還有 redux-sage,immutable(多圖預警),因爲知識點比較多,建議先收藏(收藏等於學會了 ),對你有幫助的話就給個贊👍

認識純函數

JavaScript純函數

  • 函數式編程中有一個概念叫純函數, JavaScript符合函數式編程的範式, 因此也有純函數的概念
  • React中,純函數的概念很是重要,在接下來咱們學習的Redux中也很是重要,因此咱們必須來回顧一下純函數
  • 純函數的定義簡單總結一下:react

    • 純函數指的是, 每次給相同的參數, 必定返回相同的結果
    • 函數在執行過程當中, 不能產生反作用
  • 純函數( Pure Function )的注意事項:ios

    • 在純函數中不能使用隨機數
    • 不能使用當前的時間或日期, 由於結果是會變的
    • 不能使用或者修改全局狀態, 好比DOM,文件、數據庫等等(由於若是全局狀態改變了,它就會影響函數的結果)
    • 純函數中的參數不能變化,不然函數的結果就會改變

React中的純函數

  • 爲何純函數在函數式編程中很是重要呢?git

    • 由於你能夠安心的寫和安心的用
    • 你在寫的時候保證了函數的純度,實現本身的業務邏輯便可,不須要關心傳入的內容或者函數體依賴了外部的變量
    • 你在用的時候,你肯定你的輸入內容不會被任意篡改,而且本身肯定的輸入,必定會有肯定的輸出
  • React很是靈活,但它也有一個嚴格的規則:web

    • 全部React組件都必須像"純函數"同樣保護它們的"props"不被更改

認識Redux

爲何須要redux

  • JavaScript開發的應用程序, 已經變得很是複雜了:算法

    • JavaScript須要管理的狀態愈來愈多, 愈來愈複雜了
    • 這些狀態包括服務器返回的數據, 用戶操做的數據等等, 也包括一些UI的狀態
  • 管理不斷變化的state是很是困難的:chrome

    • 狀態之間相互存在依賴, 一個狀態的變化會引發另外一個狀態的變化, View頁面也有可能會引發狀態的變化
    • 當程序複雜時, state在何時, 由於什麼緣由發生了變化, 發生了怎樣的變化, 會變得很是難以控制和追蹤

React的做用

  • React只是在視圖層幫助咱們解決了DOM的渲染過程, 可是state依然是留給咱們本身來管理:數據庫

    • 不管是組件定義本身的state,仍是組件之間的通訊經過props進行傳遞
    • 也包括經過Context進行數據之間的共享
    • React主要負責幫助咱們管理視圖,state如何維護最終仍是咱們本身來決定

  • Redux就是一個幫助咱們管理State的容器:編程

    • ReduxJavaScript的狀態容器, 提供了可預測的狀態管理
  • Redux除了和React一塊兒使用以外, 它也能夠和其餘界面庫一塊兒來使用(好比Vue), 而且它很是小 (包括依賴在內,只有2kb)

Redux的核心理念-Store

  • Redux的核心理念很是簡單
  • 好比咱們有一個朋友列表須要管理:redux

    • 若是咱們沒有定義統一的規範來操做這段數據,那麼整個數據的變化就是沒法跟蹤的
    • 好比頁面的某處經過products.push的方式增長了一條數據
    • 好比另外一個頁面經過products[0].age = 25修改了一條數據
  • 整個應用程序錯綜複雜,當出現bug時,很難跟蹤到底哪裏發生的變化

Redux的核心理念-action

  • Redux要求咱們經過action來更新stateaxios

    • 全部數據的變化, 必須經過dispatch來派發action來更新
    • action是一個普通的JavaScript對象,用來描述此次更新的typecontent
  • 好比下面就是幾個更新friendsaction:

    • 強制使用action的好處是能夠清晰的知道數據到底發生了什麼樣的變化,全部的數據變化都是可跟追蹤、可預測的
    • 固然,目前咱們的action是固定的對象,真實應用中,咱們會經過函數來定義,返回一個action

Redux的核心理念-reducer

  • 可是如何將stateaction聯繫在一塊兒呢? 答案就是reducer

    • reducer是一個純函數
    • reducer作的事情就是將傳入的stateaction結合起來來生成一個新的state

Redux的三大原則

  • 單一數據源

    • 整個應用程序的state被存儲在一顆object tree中, 而且這個object tree只存儲在一個store
    • Redux並無強制讓咱們不能建立多個Store,可是那樣作並不利於數據的維護
    • 單一的數據源可讓整個應用程序的state變得方便維護、追蹤、修改
  • State是隻讀的

    • 惟一修改state的方法必定是觸發action, 不要試圖在其它的地方經過任何的方式來修改state
    • 這樣就確保了View或網絡請求都不能直接修改state,它們只能經過action來描述本身想要如何修改state
    • 這樣能夠保證全部的修改都被集中化處理,而且按照嚴格的順序來執行,因此不須要擔憂race condition(竟態)的問題
  • 使用純函數來執行修改

    • 經過reducer將舊 stateaction 聯繫在一塊兒, 而且返回一個新的state
    • 隨着應用程序的複雜度增長,咱們能夠將reducer拆分紅多個小的reducers,分別操做不一樣state tree的一部分
    • 可是全部的reducer都應該是純函數,不能產生任何的反作用

Redux的基本使用

Redux中核心的API

redux的安裝: yarn add redux

  1. createStore 能夠用來建立 store對象
  2. store.dispatch 用來派發 action , action 會傳遞給 store
  3. reducer接收action,reducer計算出新的狀態並返回它 (store負責調用reducer)
  4. store.getState 這個方法能夠幫助獲取 store 裏邊全部的數據內容
  5. store.subscribe方法可讓讓咱們訂閱 store 的改變,只要 store 發生改變, store.subscribe 這個函數接收的這個回調函數就會被執行

小結

  1. 建立sotore, 決定 store 要保存什麼狀態
  2. 建立action, 用戶在程序中實現什麼操做
  3. 建立reducer, reducer 接收 action 並返回更新的狀態

Redux的使用過程

  1. 建立一個對象, 做爲咱們要保存的狀態
  2. 建立Store來存儲這個state

    • 建立store時必須建立reducer
    • 咱們能夠經過 store.getState 來獲取當前的state
  3. 經過action來修改state

    • 經過dispatch來派發action
    • 一般action中都會有type屬性,也能夠攜帶其餘的數據
  4. 修改reducer中的處理代碼

    • 這裏必定要記住,reducer是一個純函數,不能直接修改state
    • 後面會講到直接修改state帶來的問題
  5. 能夠在派發action以前,監聽store的變化
import { createStore } from 'redux'

// 1.初始化state
const initState = { counter: 0 }

// 2.reducer純函數 不能修改傳遞的state
function reducer(state = initState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, counter: state.counter + 1 }
    case 'ADD_COUNTER':
      return { ...state, counter: state.counter + action.num }
    default:
      return state
  }
}

// 3.store 參數放一個reducer
const store = createStore(reducer)

// 4.action
const action1 = { type: 'INCREMENT' }
const action2 = { type: 'ADD_COUNTER', num: 2 }

// 5.訂閱store的修改
store.subscribe(() => {
  console.log('state發生了改變: ', store.getState().counter)
})

// 6.派發action
store.dispatch(action1)
store.dispatch(action2)

redux原理圖

Redux結構劃分

  • 若是咱們將全部的邏輯代碼寫到一塊兒, 那麼當redux變得複雜時代碼就難以維護
  • 對代碼進行拆分, 將store、reducer、action、constants拆分紅一個個文件

<details>
<summary>拆分目錄</summary>

</details>

Redux使用流程

redux

Redux官方流程圖

redux-flow

React-Redux的使用

redux融入react代碼(案例)

  • redux融入react代碼案例:

    • Home組件:其中會展現當前的counter值,而且有一個+1和+5的按鈕
    • Profile組件:其中會展現當前的counter值,而且有一個-1和-5的按鈕

  • 核心代碼主要是兩個:

    • componentDidMount 中訂閱數據的變化,當數據發生變化時從新設置 counter
    • 在發生點擊事件時,調用storedispatch來派發對應的action

自定義connect函數

當咱們多個組件使用redux時, 重複的代碼太多了, 好比: 訂閱state取消訂閱state 或 派發action獲取state

將重複的代碼進行封裝, 將不一樣的statedispatch做爲參數進行傳遞

//  connect.js 
import React, { PureComponent } from 'react'
import { StoreContext } from './context'
/**
 * 1.調用該函數: 返回一個高階組件
 *      傳遞須要依賴 state 和 dispatch 來使用state或經過dispatch來改變state
 *
 * 2.調用高階組件:
 *      傳遞該組件須要依賴 store 的組件
 *
 * 3.主要做用:
 *      將重複的代碼抽取到高階組件中,並將該組件依賴的 state 和 dispatch
 *      經過調用mapStateToProps()或mapDispatchToProps()函數
 *      並將該組件依賴的state和dispatch供該組件使用,其餘使用store的組件沒必要依賴store
 *
 * 4.connect.js: 優化依賴
 *      目的:可是上面的connect函數有一個很大的缺陷:依賴導入的 store
 *      優化:正確的作法是咱們提供一個Provider,Provider來自於咱們
 *             Context,讓用戶將store傳入到value中便可;
 */
export function connect(mapStateToProps, mapDispatchToProps) {
  return function enhanceComponent(WrapperComponent) {
    class EnhanceComponent extends PureComponent {
      constructor(props, context) {
        super(props, context)

        // 組件依賴的state
        this.state = {
          storeState: mapStateToProps(context.getState()),
        }
      }

      // 訂閱數據發生變化,調用setState從新render
      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() => {
          this.setState({
            centerStore: mapStateToProps(this.context.getState()),
          })
        })
      }

      // 組件被卸載取消訂閱
      componentWillUnmount() {
        this.unsubscribe()
      }

      render() {
        // 下面的WrapperComponent至關於 home 組件(就是你傳遞的組件)
        // 你須要將該組件須要依賴的state和dispatch做爲props進行傳遞
        return (
          <WrapperComponent
            {...this.props}
            {...mapStateToProps(this.context.getState())}
            {...mapDispatchToProps(this.context.dispatch)}
          />
        )
      }
    }
    // 取出Provider提供的value
    EnhanceComponent.contextType = StoreContext
    return EnhanceComponent
  }
}

// home.js
// 定義組件依賴的state和dispatch
const mapStateToProps = state => ({
  counter: state.counter,
})

const mapDispatchToProps = dispatch => ({
  increment() {
    dispatch(increment())
  },
  addNumber(num) {
    dispatch(addAction(num))
  },
})
export default connect(mapStateToProps,mapDispatchToProps)(依賴redux的組件)

react-redux使用

  • 開始以前須要強調一下,reduxreact沒有直接的關係,你徹底能夠在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux
  • 儘管這樣說,redux依然是和React或者Deku的庫結合的更好,由於他們是經過state函數來描述界面的狀態,Redux能夠發射狀態的更新,讓他們做出相應。
  • 雖然咱們以前已經實現了connectProvider這些幫助咱們完成鏈接redux、react的輔助工具,可是實際上redux官方幫助咱們提供了 react-redux 的庫,能夠直接在項目中使用,而且實現的邏輯會更加的嚴謹和高效
  • 安裝react-redux

    • yarn add react-redux
// 1.index.js
import { Provider } from 'react-redux'
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

// 2.home.js
import { connect } from 'react-redux'
// 定義須要依賴的state和dispatch (函數須要返回一個對象)
export default connect(mapStateToProps, mapDispatchToProps)(About)

react-redux源碼導讀

Redux-Middleware中間件

組件中異步操做

  • 在以前簡單的案例中,redux中保存的counter是一個本地定義的數據

    • 咱們能夠直接經過同步的操做來dispatch actionstate就會被當即更新。
    • 可是真實開發中,redux中保存的不少數據可能來自服務器,咱們須要進行異步的請求,再將數據保存到redux
  • 網絡請求能夠在class組件的componentDidMount中發送,因此咱們能夠有這樣的結構:

redux中異步操做

  • 上面的代碼有一個缺陷:

    • 咱們必須將網絡請求的異步代碼放到組件的生命週期中來完成
  • 爲何將網絡請求的異步代碼放在redux中進行管理?

    • 後期代碼量的增長,若是把網絡請求異步函數放在組件的生命週期裏,這個生命週期函數會變得愈來愈複雜,組件就會變得愈來愈大
    • 事實上,網絡請求到的數據也屬於狀態管理的一部分,更好的一種方式應該是將其也交給redux來管理

  • 可是在redux中如何能夠進行異步的操做呢?

    • 使用中間件 (Middleware)
    • 學習過ExpressKoa框架的童鞋對中間件的概念必定不陌生
    • 在這類框架中,Middleware能夠幫助咱們在請求和響應之間嵌入一些操做的代碼,好比cookie解析、日誌記錄、文件壓縮等操做

理解中間件(重點)

  • redux也引入了中間件 (Middleware) 的概念:

    • 這個<font color='red'>中間件的目的是在dispatchaction和最終達到的reducer之間,擴展一些本身的代碼</font>
    • 好比日誌記錄、調用異步接口、添加代碼調試功能等等

redux-middlware

  • redux-thunk是如何作到讓咱們能夠發送異步的請求呢?

    • 默認狀況下的dispatch(action)action須要是一個JavaScript的對象
    • redux-thunk可讓dispatch(action函數), action<font color='red'>能夠是一個函數</font>
    • 該函數會被調用, 而且會傳給這個函數兩個參數: 一個dispatch函數和getState函數

      • dispatch函數用於咱們以後再次派發action
      • getState函數考慮到咱們以後的一些操做須要依賴原來的狀態,用於讓咱們能夠獲取以前的一些狀態

redux-thunk的使用

  1. 安裝redux-thunk

    • yarn add redux-thunk
  2. 在建立store時傳入應用了middlewareenhance函數

    • 經過applyMiddleware來結合多個Middleware, 返回一個enhancer
    • enhancer做爲第二個參數傳入到createStore

      image-20200821182447344

  3. 定義返回一個函數的action

    • 注意:這裏不是返回一個對象了,而是一個函數
    • 該函數在dispatch以後會被執行

<details>
<summary>查看代碼</summary>
<pre>import { createStore, applyMiddleware } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'<br/>
const store = createStore(
reducer,
applyMiddleware(thunk) // applyMiddleware可使用中間件模塊
)
export default store
</pre></details>

redux-devtools

redux-devtools插件

  • 咱們以前講過,redux能夠方便的讓咱們對狀態進行跟蹤和調試,那麼如何作到呢?

    • redux官網爲咱們提供了redux-devtools的工具
    • 利用這個工具,咱們能夠知道每次狀態是如何被修改的,修改先後的狀態變化等等
  • 使用步驟:

    • 第一步:在瀏覽器上安裝redux-devtools擴展插件
    • 第二步:在redux中集成devtools的中間件
// store.js 開啓redux-devtools擴展
import { createStore, applyMiddleware, compose } from 'redux'

// composeEnhancers函數
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose

// 經過applyMiddleware來結合多個Middleware,返回一個enhancer
const enhancer = applyMiddleware(thankMiddleware)

// 經過enhancer做爲第二個參數傳遞createStore中
const store = createStore(reducer, composeEnhancers(enhancer))

export default store

redux-sage

generator

Generator 函數是 ES6 提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣

Generator 函數有多種理解角度。語法上,首先能夠把它理解成,Generator 函數是一個狀態機,封裝了多個內部狀態。

// 生成器函數的定義
// 默認返回: Generator
function* foo() {
  console.log('111')
  yield 'hello'
  console.log('222')
  yield 'world'
  console.log('333')
  yield 'jane'
  console.log('444')
}
// iterator: 迭代器
const result = foo()
console.log(result)

// 使用迭代器
// 調用next,就會消耗一次迭代器
const res1 = result.next()
console.log(res1) // {value: "hello", done: false}
const res2 = result.next()
console.log(res2) // {value: "world", done: false}
const res3 = result.next()
console.log(res3) // {value: "jane", done: false}
const res4 = result.next()
console.log(res4) // {value: undefined, done: true}

redux-sage流程

redux-saga的使用

  • redux-saga是另外一個比較經常使用在redux發送異步請求的中間件,它的使用更加的靈活
  • Redux-saga的使用步驟以下

    1. 安裝redux-sage: yarn add redux-saga
    2. 集成redux-saga中間件

      • 引入 createSagaMiddleware 後, 須要建立一個 sagaMiddleware
      • 而後經過 applyMiddleware 使用這個中間件,接着建立 saga.js 這個文件
      • 啓動中間件的監聽過程, 而且傳入要監聽的saga
    3. saga.js文件的編寫

      • takeEvery:能夠傳入多個監聽的actionType,每個均可以被執行(對應有一個takeLatest,會取消前面的)
      • put:在saga中派發action再也不是經過dispatch, 而是經過put
      • all:能夠在yield的時候put多個action
// store.js
import createSageMiddleware from 'redux-saga'
import saga from './saga'
// 1.建立sageMiddleware中間件
const sagaMiddleware = createSageMiddleware()
// 2.應用一些中間件
const enhancer = applyMiddleware(sagaMiddleware)
const store = createStore(reducer,composeEnhancers(enhancer))

sagaMiddleware.run(saga)
export default store

// saga.js
import { takeEvery, put, all } from 'redux-saga/effects'
import { FETCH_HOME_DATA } from './constant'

function* fetchHomeData(action) {
  const res = yield axios.get('http://123.207.32.32:8000/home/multidata')
  const banners = res.data.data.banner.list
  const recommends = res.data.data.recommend.list
  // dispatch action 提交action,redux-sage提供了put
  yield all([
    yield put(changeBannersAction(banners)),
    yield put(changeRecommendAction(recommends)),
  ])
}

function* mySaga() {
  // 參數一:要攔截的actionType
  // 參數二:生成器函數
  yield all([
    takeEvery(FETCH_HOME_DATA, fetchHomeData),
  ])
}

export default mySaga

reducer代碼拆分

Reducer代碼拆分

  • 咱們來看一下目前咱們的reducer

    • 當前這個reducer既有處理counter的代碼,又有處理home頁面的數據
    • 後續counter相關的狀態或home相關的狀態會進一步變得更加複雜
    • 咱們也會繼續添加其餘的相關狀態,好比購物車、分類、歌單等等
    • 若是將全部的狀態都放到一個reducer中進行管理,隨着項目的日趨龐大,必然會形成代碼臃腫、難以維護
  • 所以,咱們能夠對reducer進行拆分:

    • 咱們先抽取一個對counter處理的reducer
    • 再抽取一個對home處理的reducer
    • 將它們合併起來

Reducer文件拆分

  • 目前咱們已經將不一樣的狀態處理拆分到不一樣的reducer中,咱們來思考:

    • 雖然已經放到不一樣的函數了,可是這些函數的處理依然是在同一個文件中,代碼很是的混亂
    • 另外關於reducer中用到的constantaction等咱們也依然是在同一個文件中;

combineReducers函數

  • 目前咱們合併的方式是經過每次調用reducer函數本身來返回一個新的對象
  • 事實上,redux給咱們提供了一個combineReducers函數能夠方便的讓咱們對多個reducer進行合併
import { combineReducers } from 'redux'
import { reducer as counterReducer } from './count'
import { reducer as homeReducer } from './home'

export const reducer = combineReducers({
  counterInfo: counterReducer,
  homeInfo: homeReducer,
})
  • 那麼combineReducers是如何實現的呢?

    • 它將咱們傳遞的reducer合併成一個對象, 最終返回一個combination函數
    • 在執行combination函數過程當中, 會經過判斷先後返回的數據是否相同來決定返回以前的state仍是新的state

immutableJs

數據可變形的問題

  • React開發中,咱們老是會強調數據的不可變性:

    • 不管是類組件中的state,仍是reduex中管理的state
    • 事實上在整個JavaScript編碼的過程當中,數據的不可變性都是很是重要的
  • 數據的可變性引起的問題(案例):

    • 咱們明明沒有修改obj,只是修改了obj2,可是最終obj也被咱們修改掉了
    • 緣由很是簡單,對象是引用類型,它們指向同一塊內存空間,兩個引用均可以任意修改
const obj1 = { name: 'jane', age: 18 }
const obj2 = obj1
obj1.name = 'kobe'
console.log(obj2.name) // kobe
  • 有沒有辦法解決上面的問題呢?

    • 進行對象的拷貝便可:Object.assign或擴展運算符
  • 這種對象的淺拷貝有沒有問題呢?

    • 從代碼的角度來講,沒有問題,也解決了咱們實際開發中一些潛在風險
    • 從性能的角度來講,有問題,若是對象過於龐大,這種拷貝的方式會帶來性能問題以及內存浪費
  • 有人會說,開發中不都是這樣作的嗎?

    • 歷來如此,即是對的嗎?

認識ImmutableJS

  • 爲了解決上面的問題,出現了Immutable對象的概念:

    • Immutable對象的特色是隻要修改了對象,就會返回一個新的對象,舊的對象不會發生改變;
  • 可是這樣的方式就不會浪費內存了嗎?

    • 爲了節約內存,又出現了一個新的算法:Persistent Data Structure(持久化數據結構或一致性數據結構)
  • 固然,咱們一聽到持久化第一反應應該是數據被保存到本地或者數據庫,可是這裏並非這個含義:

    • 用一種數據結構來保存數據
    • 當數據被修改時,會返回一個對象,可是新的對象會盡量的利用以前的數據結構而不會對內存形成浪費,如何作到這一點呢?結構共享:

  • 安裝Immutable: yarn add immutable

ImmutableJS常見API

注意:我這裏只是演示了一些API,更多的方式能夠參考官網

做用:不會修改原有數據結構,返回一個修改後新的拷貝對象

  • JavaScripImutableJS直接的轉換

    • 對象轉換成Immutable對象:Map
    • 數組轉換成Immtable數組:List
    • 深層轉換:fromJS
const im = Immutable
// 對象轉換成Immutable對象
const info = {name: 'kobe', age: 18}
const infoIM = im.Map()

// 數組轉換成Immtable數組
const names = ["abc", "cba", "nba"]
const namesIM = im.List(names)
  • ImmutableJS的基本操做:

    • 修改數據:set(property, newVal)

      • 返回值: 修改後新的數據結構
    • 獲取數據:get(property/index)
    • 獲取深層Immutable對象數據(子屬性也是Immutable對象): getIn(['recommend', 'topBanners'])
// set方法 不會修改infoIM原有數據結構,返回修改後新的數據結構
const newInfo2IM = infoIM.set('name', 'james')
const newNamesIM = namesIM.set(0, 'why')

// get方法
console.log(infoIM.get('name'))// -> kobe
console.log(namesIM.get(0))// -> abc

結合Redux管理數據

  1. ImmutableJS重構redux

    • yarn add Immutable
    • yarn add redux-immutable
  2. 使用redux-immutable中的combineReducers;
  3. 全部的reducer中的數據都轉換成Immutable類型的數據

FAQ

React中的state如何管理

  • 目前項目中採用的state管理方案(參考便可):

    • 相關的組件內部能夠維護的狀態,在組件內部本身來維護
    • 只要是須要共享的狀態,都交給redux來管理和維護
    • 從服務器請求的數據(包括請求的操做) ,交給redux來維護

前言

hello你們好,我是風不識途,最近一直在整理 redux系列文章,發現對於初學者不太友好,關係錯綜複雜,難卻是不太難,就是比較複雜 (其實寫比較少),因此這篇帶你全面瞭解 redux、react-redux、redux-thunk還有 redux-sage,immutable(多圖預警),因爲知識點比較多,建議先收藏(收藏等於學會了 ),對你有用的話就給個贊👍

認識純函數

JavaScript純函數

  • 函數式編程中有一個概念叫純函數, JavaScript符合函數式編程的範式, 因此也有純函數的概念

  • React中,純函數的概念很是重要,在接下來咱們學習的Redux中也很是重要,因此咱們必須來回顧一下純函數

  • 純函數的維基百科定義(瞭解便可)
    
    ​
  • 純函數的定義簡單總結一下:

    *   純函數指的是, 每次給相同的參數, 必定返回相同的結果
        
    *   函數在執行過程當中, 不能產生反作用
  • **純函數( `Pure Function` )的注意事項:**
    
    ​

React中的純函數

  • 爲何純函數在函數式編程中很是重要呢?

    *   由於你能夠安心的寫和安心的用
        
    *   你在寫的時候保證了函數的純度,實現本身的業務邏輯便可,不須要關心傳入的內容或者函數體依賴了外部的變量
        
    *   你在用的時候,你肯定你的輸入內容不會被任意篡改,而且本身肯定的輸入,必定會有肯定的輸出
  • React很是靈活,但它也有一個嚴格的規則:

    *   全部React組件都必須像"純函數"同樣保護它們的"props"不被更改

認識Redux

爲何須要redux

  • JavaScript開發的應用程序, 已經變得很是複雜了:

    *   `JavaScript`**須要管理的狀態愈來愈多**, 愈來愈複雜了
        
    *   這些狀態包括服務器返回的數據, 用戶操做的數據等等, 也包括一些`UI`的狀態
  • 管理不斷變化的state是很是困難的:

    *   **狀態之間相互存在依賴**, 一個狀態的變化會引發另外一個狀態的變化, `View`頁面也有可能會引發狀態的變化
        
    *   當程序複雜時, `state`在何時, 由於什麼緣由發生了變化, 發生了怎樣的變化, 會變得很是難以控制和追蹤

React的做用

  • React只是在視圖層幫助咱們解決了DOM的渲染過程, 可是state依然是留給咱們本身來管理:

    *   不管是組件定義本身的`state`,仍是組件之間的通訊經過`props`進行傳遞
        
    *   也包括經過`Context`進行數據之間的共享
        
    *   `React`主要負責幫助咱們管理視圖,`state`如何維護最終仍是咱們本身來決定
![](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132319.png)
  • Redux就是一個幫助咱們管理State的容器:

    *   `Redux`是`JavaScript`的狀態容器, 提供了可預測的狀態管理
  • Redux除了和React一塊兒使用以外, 它也能夠和其餘界面庫一塊兒來使用(好比Vue), 而且它很是小 (包括依賴在內,只有2kb)

Redux的核心理念-Store

  • Redux的核心理念很是簡單

  • 好比咱們有一個朋友列表須要管理:

    *   **若是咱們沒有定義統一的規範來操做這段數據,那麼整個數據的變化就是沒法跟蹤的**
        
    *   好比頁面的某處經過`products.push`的方式增長了一條數據
        
    *   好比另外一個頁面經過`products[0].age = 25`修改了一條數據
  • 整個應用程序錯綜複雜,當出現bug時,很難跟蹤到底哪裏發生的變化

Redux的核心理念-action

  • Redux要求咱們經過action來更新state

    *   **全部數據的變化, 必須經過**`dispatch`來派發`action`來更新
        
    *   `action`是一個普通的`JavaScript`對象,用來描述此次更新的`type`和`content`
  • 好比下面就是幾個更新friendsaction:

    *   強制使用`action`的好處是能夠清晰的知道數據到底發生了什麼樣的變化,全部的數據變化都是可跟追蹤、可預測的
        
    *   固然,目前咱們的`action`是固定的對象,真實應用中,咱們會經過函數來定義,返回一個`action`

Redux的核心理念-reducer

  • 可是如何將stateaction聯繫在一塊兒呢? 答案就是reducer

    *   `reducer`是一個純函數
        
    *   `reducer`作的事情就是將傳入的`state`和`action`結合起來來生成一個新的`state`

Redux的三大原則

  • 單一數據源

    *   整個應用程序的`state`被存儲在一顆`object tree`中, 而且這個`object tree`只存儲在一個`store`
        
    *   `Redux`並無強制讓咱們不能建立多個`Store`,可是那樣作並不利於數據的維護
        
    *   單一的數據源可讓整個應用程序的`state`變得方便維護、追蹤、修改
  • State是隻讀的

    *   惟一修改`state`的方法必定是觸發`action`, 不要試圖在其它的地方經過任何的方式來修改`state`
        
    *   這樣就確保了`View`或網絡請求都不能直接修改`state`,它們只能經過`action`來描述本身想要如何修改`state`
        
    *   這樣能夠保證全部的修改都被集中化處理,而且按照嚴格的順序來執行,因此不須要擔憂`race condition`(竟態)的問題
  • 使用純函數來執行修改

    *   經過`reducer`將舊 `state` 和 `action` 聯繫在一塊兒, 而且返回一個新的`state`
        
    *   隨着應用程序的複雜度增長,咱們能夠將`reducer`拆分紅多個小的`reducers`,分別操做不一樣`state tree`的一部分
        
    *   可是全部的`reducer`都應該是純函數,不能產生任何的反作用

Redux的基本使用

Redux中核心的API

redux的安裝: yarn add redux

  1. createStore 能夠用來建立 store對象

  2. store.dispatch 用來派發 action, action會傳遞給 store

  3. reducer接收action,reducer計算出新的狀態並返回它 (store負責調用reducer)

  4. store.getState 這個方法能夠幫助獲取 store 裏邊全部的數據內容

  5. store.subscribe方法可讓讓咱們訂閱 store 的改變,只要 store 發生改變, store.subscribe 這個函數接收的這個回調函數就會被執行

小結

  1. 建立sotore, 決定 store 要保存什麼狀態

  2. 建立action, 用戶在程序中實現什麼操做

  3. 建立reducer, reducer 接收 action 並返回更新的狀態

Redux的使用過程

  1. 建立一個對象, 做爲咱們要保存的狀態

  2. 建立Store來存儲這個state

    *   建立`store`時必須建立`reducer`
        
    *   咱們能夠經過 `store.getState` 來獲取當前的`state`
  3. 經過action來修改state

    *   經過`dispatch`來派發`action`
        
    *   一般`action`中都會有`type`屬性,也能夠攜帶其餘的數據
  4. 修改reducer中的處理代碼

    *   這裏必定要記住,`reducer`是一個**純函數**,不能直接修改`state`
        
    *   後面會講到直接修改`state`帶來的問題
  5. 能夠在派發action以前,監聽store的變化

import { createStore } from 'redux'

// 1.初始化state
const initState = { counter: 0 }

// 2.reducer純函數 不能修改傳遞的state
function reducer(state = initState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, counter: state.counter + 1 }
case 'ADD_COUNTER':
return { ...state, counter: state.counter + action.num }
default:
return state
}
}

// 3.store 參數放一個reducer
const store = createStore(reducer)

// 4.action
const action1 = { type: 'INCREMENT' }
const action2 = { type: 'ADD_COUNTER', num: 2 }

// 5.訂閱store的修改
store.subscribe(() => {
console.log('state發生了改變: ', store.getState().counter)
})

// 6.派發action
store.dispatch(action1)
store.dispatch(action2)

redux原理圖

Redux結構劃分

  • 若是咱們將全部的邏輯代碼寫到一塊兒, 那麼當redux變得複雜時代碼就難以維護

  • 對代碼進行拆分, 將store、reducer、action、constants拆分紅一個個文件

拆分目錄

Redux使用流程

redux

Redux官方流程圖

redux-flow

React-Redux的使用

redux融入react代碼(案例)

  • redux融入react代碼案例:

    *   `Home`組件:其中會展現當前的`counter`值,而且有一個+1和+5的按鈕
        
    *   `Profile`組件:其中會展現當前的`counter`值,而且有一個-1和-5的按鈕
![](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132516.png)
  • 核心代碼主要是兩個:

    *   在 `componentDidMount`中訂閱數據的變化,當數據發生變化時從新設置 `counter`
        
    *   在發生點擊事件時,調用`store`的`dispatch`來派發對應的`action`

自定義connect函數

當咱們多個組件使用redux時, 重複的代碼太多了, 好比: 訂閱state取消訂閱state 或 派發action獲取state

將重複的代碼進行封裝, 將不一樣的statedispatch做爲參數進行傳遞

// connect.js
import React, { PureComponent } from 'react'
import { StoreContext } from './context'
/**

  • 1.調用該函數: 返回一個高階組件
  •     傳遞須要依賴 state 和 dispatch 來使用state或經過dispatch來改變state
    *
  • 2.調用高階組件:
  •     傳遞該組件須要依賴 store 的組件
    *
  • 3.主要做用:
  •     將重複的代碼抽取到高階組件中,並將該組件依賴的 state 和 dispatch
  •     經過調用mapStateToProps()或mapDispatchToProps()函數
  •     並將該組件依賴的state和dispatch供該組件使用,其餘使用store的組件沒必要依賴store
    *
  • 4.connect.js: 優化依賴
  •     目的:可是上面的connect函數有一個很大的缺陷:依賴導入的 store
  •     優化:正確的作法是咱們提供一個Provider,Provider來自於咱們
  • Context,讓用戶將store傳入到value中便可;
    */

export function connect(mapStateToProps, mapDispatchToProps) {
return function enhanceComponent(WrapperComponent) {
class EnhanceComponent extends PureComponent {
constructor(props, context) {
super(props, context)

// 組件依賴的state
this.state = {
storeState: mapStateToProps(context.getState()),
}
}

// 訂閱數據發生變化,調用setState從新render
componentDidMount() {
this.unsubscribe = this.context.subscribe(() => {
this.setState({
centerStore: mapStateToProps(this.context.getState()),
})
})
}

// 組件被卸載取消訂閱
componentWillUnmount() {
this.unsubscribe()
}

render() {
// 下面的WrapperComponent至關於 home 組件(就是你傳遞的組件)
// 你須要將該組件須要依賴的state和dispatch做爲props進行傳遞
return (
<WrapperComponent
{...this.props}
{...mapStateToProps(this.context.getState())}
{...mapDispatchToProps(this.context.dispatch)}
/>
)
}
}
// 取出Provider提供的value
EnhanceComponent.contextType = StoreContext
return EnhanceComponent
}
}

// home.js
// 定義組件依賴的state和dispatch
const mapStateToProps = state => ({
counter: state.counter,
})

const mapDispatchToProps = dispatch => ({
increment() {
dispatch(increment())
},
addNumber(num) {
dispatch(addAction(num))
},
})
export default connect(mapStateToProps,mapDispatchToProps)(依賴redux的組件)

react-redux使用

  • 開始以前須要強調一下,reduxreact沒有直接的關係,你徹底能夠在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux

  • 儘管這樣說,redux依然是和React或者Deku的庫結合的更好,由於他們是經過state函數來描述界面的狀態,Redux能夠發射狀態的更新,讓他們做出相應。

  • 雖然咱們以前已經實現了connectProvider這些幫助咱們完成鏈接redux、react的輔助工具,可是實際上redux官方幫助咱們提供了react-redux 的庫,能夠直接在項目中使用,而且實現的邏輯會更加的嚴謹和高效

  • 安裝react-redux

    *   `yarn add react-redux`

// 1.index.js
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

// 2.home.js
import { connect } from 'react-redux'
// 定義須要依賴的state和dispatch (函數須要返回一個對象)
export default connect(mapStateToProps, mapDispatchToProps)(About)

react-redux源碼導讀

Redux-Middleware中間件

組件中異步操做

  • 在以前簡單的案例中,redux中保存的counter是一個本地定義的數據

    *   咱們能夠直接經過同步的操做來`dispatch action`,`state`就會被當即更新。
        
    *   可是真實開發中,`redux`中保存的**不少數據可能來自服務器**,咱們須要進行**異步的請求**,再將數據保存到`redux`中
  • 網絡請求能夠在class組件的componentDidMount中發送,因此咱們能夠有這樣的結構:

redux中異步操做

  • 上面的代碼有一個缺陷:

    *   咱們必須將**網絡請求**的異步代碼放到組件的生命週期中來完成
  • 爲何將網絡請求的異步代碼放在redux中進行管理?

    *   後期代碼量的增長,若是把網絡請求異步函數放在組件的生命週期裏,這個生命週期函數會變得愈來愈複雜,組件就會變得愈來愈大
        
    *   事實上,**網絡請求到的數據也屬於狀態管理的一部分**,更好的一種方式應該是將其也交給`redux`來管理

  • 可是在redux中如何能夠進行異步的操做呢?

    *   **使用中間件 (Middleware)**
        
    *   學習過`Express`或`Koa`框架的童鞋對中間件的概念必定不陌生
        
    *   在這類框架中,`Middleware`能夠幫助咱們在**請求和響應之間嵌入一些操做的代碼**,好比cookie解析、日誌記錄、文件壓縮等操做

理解中間件(重點)

  • redux也引入了中間件 (Middleware) 的概念:

    *   這個中間件的目的是在`dispatch`的`action`和最終達到的`reducer`之間,擴展一些本身的代碼
        
    *   好比日誌記錄、**調用異步接口**、添加代碼調試功能等等

redux-middlware

  • redux-thunk是如何作到讓咱們能夠發送異步的請求呢?

    *   默認狀況下的`dispatch(action)`,`action`須要是一個`JavaScript`的對象
        
    *   `redux-thunk`可讓`dispatch`(`action`函數), `action`**能夠是一個函數**
        
    *   該函數會被調用, 而且會傳給這個函數兩個參數: 一個`dispatch`函數和`getState`函數
        
        *   `dispatch`函數用於咱們以後再次派發`action`
            
        *   `getState`函數考慮到咱們以後的一些操做須要依賴原來的狀態,用於讓咱們能夠獲取以前的一些狀態

redux-thunk的使用

  1. 安裝redux-thunk

    *   `yarn add redux-thunk`
  2. 在建立store時傳入應用了middlewareenhance函數

    *   經過`applyMiddleware`來結合多個`Middleware`, 返回一個`enhancer`
        
    *   將`enhancer`做爲第二個參數傳入到`createStore`中
        
        ![image-20200821182447344](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132723.png)
  3. 定義返回一個函數的action

    *   注意:這裏不是返回一個對象了,而是一個**函數**
        
    *   該函數在`dispatch`以後會被執行
![](https://gitee.com/xmkm/cloudPic/raw/master/img/20201005132817.png)

查看代碼

redux-devtools

redux-devtools插件

  • 咱們以前講過,redux能夠方便的讓咱們對狀態進行跟蹤和調試,那麼如何作到呢?

    *   `redux`官網爲咱們提供了`redux-devtools`的工具
        
    *   利用這個工具,咱們能夠知道每次狀態是如何被修改的,修改先後的狀態變化等等
  • 使用步驟:

    *   第一步:在瀏覽器上安裝[redux-devtools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd/related?utm_source=chrome-ntp-icon)擴展插件
        
    *   第二步:在`redux`中集成`devtools`的中間件

// store.js 開啓redux-devtools擴展
import { createStore, applyMiddleware, compose } from 'redux'

// composeEnhancers函數
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose

// 經過applyMiddleware來結合多個Middleware,返回一個enhancer
const enhancer = applyMiddleware(thankMiddleware)

// 經過enhancer做爲第二個參數傳遞createStore中
const store = createStore(reducer, composeEnhancers(enhancer))

export default store

redux-sage

generator

Generator函數是 ES6 提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣

Generator 函數有多種理解角度。語法上,首先能夠把它理解成,Generator函數是一個狀態機,封裝了多個內部狀態。

// 生成器函數的定義
// 默認返回: Generator
function* foo() {
console.log('111')
yield 'hello'
console.log('222')
yield 'world'
console.log('333')
yield 'jane'
console.log('444')
}
// iterator: 迭代器
const result = foo()
console.log(result)

// 使用迭代器
// 調用next,就會消耗一次迭代器
const res1 = result.next()
console.log(res1) // {value: "hello", done: false}
const res2 = result.next()
console.log(res2) // {value: "world", done: false}
const res3 = result.next()
console.log(res3) // {value: "jane", done: false}
const res4 = result.next()
console.log(res4) // {value: undefined, done: true}

redux-sage流程

redux-saga的使用

  • redux-saga是另外一個比較經常使用在redux發送異步請求的中間件,它的使用更加的靈活

  • Redux-saga的使用步驟以下

    1.  安裝`redux-sage`: `yarn add redux-saga`
        
    2.  集成`redux-saga`中間件
        
        *   引入 `createSagaMiddleware` 後, 須要建立一個 `sagaMiddleware`
            
        *   而後經過 `applyMiddleware` 使用這個中間件,接着建立 `saga.js` 這個文件
            
        *   啓動中間件的監聽過程, 而且傳入要監聽的`saga`
            
    3.  `saga.js`文件的編寫
        
        *   `takeEvery`:能夠傳入多個監聽的`actionType`,每個均可以被執行(對應有一個`takeLatest`,會取消前面的)
            
        *   `put`:在`saga`中派發`action`再也不是經過`dispatch`, 而是經過`put`
            
        *   `all`:能夠在`yield`的時候`put`多個`action`

// store.js
import createSageMiddleware from 'redux-saga'
import saga from './saga'
// 1.建立sageMiddleware中間件
const sagaMiddleware = createSageMiddleware()
// 2.應用一些中間件
const enhancer = applyMiddleware(sagaMiddleware)
const store = createStore(reducer,composeEnhancers(enhancer))

sagaMiddleware.run(saga)
export default store

// saga.js
import { takeEvery, put, all } from 'redux-saga/effects'
import { FETCH_HOME_DATA } from './constant'

function* fetchHomeData(action) {
const res = yield axios.get('http://123.207.32.32:8000/hom...
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
// dispatch action 提交action,redux-sage提供了put
yield all([
yield put(changeBannersAction(banners)),
yield put(changeRecommendAction(recommends)),
])
}

function* mySaga() {
// 參數一:要攔截的actionType
// 參數二:生成器函數
yield all([
takeEvery(FETCH_HOME_DATA, fetchHomeData),
])
}

export default mySaga

reducer代碼拆分

Reducer代碼拆分

  • 咱們來看一下目前咱們的reducer

    *   當前這個`reducer`既有處理`counter`的代碼,又有處理`home`頁面的數據
        
    *   後續`counter`相關的狀態或`home`相關的狀態會進一步變得更加複雜
        
    *   咱們也會繼續添加其餘的相關狀態,好比購物車、分類、歌單等等
        
    *   若是將全部的狀態都放到一個`reducer`中進行管理,隨着項目的日趨龐大,必然會形成代碼臃腫、難以維護
  • 所以,咱們能夠對reducer進行拆分:

    *   咱們先抽取一個對`counter`處理的`reducer`
        
    *   再抽取一個對`home`處理的`reducer`
        
    *   將它們合併起來

Reducer文件拆分

  • 目前咱們已經將不一樣的狀態處理拆分到不一樣的reducer中,咱們來思考:

    *   雖然已經放到不一樣的函數了,可是這些函數的處理依然是在同一個文件中,代碼很是的混亂
        
    *   另外關於`reducer`中用到的`constant`、`action`等咱們也依然是在同一個文件中;

combineReducers函數

  • 目前咱們合併的方式是經過每次調用reducer函數本身來返回一個新的對象

  • 事實上,redux給咱們提供了一個combineReducers函數能夠方便的讓咱們對多個reducer進行合併

import { combineReducers } from 'redux'
import { reducer as counterReducer } from './count'
import { reducer as homeReducer } from './home'

export const reducer = combineReducers({
counterInfo: counterReducer,
homeInfo: homeReducer,
})

  • 那麼combineReducers是如何實現的呢?

    *   它將咱們傳遞的`reducer`合併成一個對象, 最終返回一個`combination`函數
        
    *   在執行`combination`函數過程當中, 會經過判斷先後返回的數據是否相同來決定返回以前的`state`仍是新的`state`

immutableJs

數據可變形的問題

  • React開發中,咱們老是會強調數據的不可變性:

    *   不管是類組件中的`state`,仍是`reduex`中管理的`state`
        
    *   事實上在整個`JavaScript`編碼的過程當中,數據的不可變性都是很是重要的
  • 數據的可變性引起的問題(案例):

    *   咱們明明沒有修改obj,只是修改了obj2,可是最終obj也被咱們修改掉了
        
    *   緣由很是簡單,對象是引用類型,它們指向同一塊內存空間,兩個引用均可以任意修改

const obj1 = { name: 'jane', age: 18 }
const obj2 = obj1
obj1.name = 'kobe'
console.log(obj2.name) // kobe

  • 有沒有辦法解決上面的問題呢?

    *   進行對象的拷貝便可:`Object.assign`或擴展運算符
  • 這種對象的淺拷貝有沒有問題呢?

    *   從代碼的角度來講,沒有問題,也解決了咱們實際開發中一些潛在風險
        
    *   從性能的角度來講,有問題,若是對象過於龐大,這種拷貝的方式會帶來性能問題以及內存浪費
  • 有人會說,開發中不都是這樣作的嗎?

    *   歷來如此,即是對的嗎?

認識ImmutableJS

  • 爲了解決上面的問題,出現了Immutable對象的概念:

    *   `Immutable`對象的特色是隻要修改了對象,就會返回一個新的對象,舊的對象不會發生改變;
  • 可是這樣的方式就不會浪費內存了嗎?

    *   爲了節約內存,又出現了一個新的算法:`Persistent Data Structure`(持久化數據結構或一致性數據結構)
  • 固然,咱們一聽到持久化第一反應應該是數據被保存到本地或者數據庫,可是這裏並非這個含義:

    *   用一種數據結構來保存數據
        
    *   當數據被修改時,會返回一個對象,可是**新的對象會盡量的利用以前的數據結構而不會對內存形成浪費**,如何作到這一點呢?結構共享:

  • 安裝Immutable: yarn add immutable

ImmutableJS常見API

注意:我這裏只是演示了一些API,更多的方式能夠參考官網

做用:不會修改原有數據結構,返回一個修改後新的拷貝對象

  • JavaScripImutableJS直接的轉換

    *   對象轉換成`Immutable`對象:`Map`
        
    *   數組轉換成`Immtable`數組:`List`
        
    *   深層轉換:`fromJS`

const im = Immutable
// 對象轉換成Immutable對象
const info = {name: 'kobe', age: 18}
const infoIM = im.Map()

// 數組轉換成Immtable數組
const names = ["abc", "cba", "nba"]
const namesIM = im.List(names)

  • ImmutableJS的基本操做:

    *   修改數據:`set(property, newVal)`
        
        *   返回值: 修改後新的數據結構
            
    *   獲取數據:`get(property/index)`
        
    *   獲取深層`Immutable`對象數據(子屬性也是`Immutable`對象): `getIn(['recommend', 'topBanners'])`

// set方法 不會修改infoIM原有數據結構,返回修改後新的數據結構
const newInfo2IM = infoIM.set('name', 'james')
const newNamesIM = namesIM.set(0, 'why')

// get方法
console.log(infoIM.get('name'))// -> kobe
console.log(namesIM.get(0))// -> abc

結合Redux管理數據

  1. ImmutableJS重構redux

    *   yarn add Immutable
        
    *   yarn add redux-immutable
  2. 使用redux-immutable中的combineReducers;

  3. 全部的reducer中的數據都轉換成Immutable類型的數據

FAQ

React中的state如何管理

  • 目前項目中採用的state管理方案(參考便可):

    *   相關的組件內部能夠維護的狀態,在組件內部本身來維護
        
    *   只要是須要共享的狀態,都交給redux來管理和維護
        
    *   從服務器請求的數據(包括請求的操做) ,交給redux來維護
相關文章
相關標籤/搜索