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

不肯清醒,寧願一直沉迷放縱。 不知歸路,寧願一世無悔追逐。      --- 王小波

本篇主要將react全家桶的產品很是精煉的提取了核心內容,精華程度堪比精油。各位大人,既然來了,客官您坐,來人,給客官看茶~~javascript

redux

前言

首先,本篇文章要求您對js,react等知識有必定的瞭解,若是未曾瞭解,建議您先看一下:React精髓!一篇全歸納(急速) java

React有props和state:react

  1. props意味着父級分發下來的屬性
  2. state意味着組件內部能夠自行管理的狀態,而且整個React沒有數據向上回溯的能力,這就是react的單向數據流

這就意味着若是是一個數據狀態很是複雜的應用,更多的時候發現React根本沒法讓兩個組件互相交流,使用對方的數據,react的經過層級傳遞數據的這種方法是很是難受的,這個時候,迫切須要一個機制,把全部的state集中到組件頂部,可以靈活的將全部state各取所需的分發給全部的組件,是的,這就是reduxajax

簡介

  1. redux是的誕生是爲了給 React 應用提供「可預測化的狀態管理」機制。
  2. Redux會將整個應用狀態(其實也就是數據)存儲到到一個地方,稱爲store
  3. 這個store裏面保存一棵狀態樹(state tree)
  4. 組件改變state的惟一方法是經過調用store的dispatch方法,觸發一個action,這個action被對應的reducer處理,因而state完成更新
  5. 組件能夠派發(dispatch)行爲(action)給store,而不是直接通知其它組件
  6. 其它組件能夠經過訂閱store中的狀態(state)來刷新本身的視圖

使用步驟

  1. 建立reducer
    • 可使用單獨的一個reducer,也能夠將多個reducer合併爲一個reducer,即:combineReducers()
    • action發出命令後將state放入reucer加工函數中,返回新的state,對state進行加工處理
  2. 建立action
    • 用戶是接觸不到state的,只能有view觸發,因此,這個action能夠理解爲指令,須要發出多少動做就有多少指令
    • action是一個對象,必須有一個叫type的參數,定義action類型
  3. 建立的store,使用createStore方法
    • store 能夠理解爲有多個加工機器的總工廠
    • 提供subscribe,dispatch,getState這些方法。

按步驟手把手實戰。

上述步驟,對應的序號,我會在相關代碼標出npm

npm install redux -S // 安裝

import { createStore } from 'redux' // 引入

const reducer = (state = {count: 0}, action) => {----------> ⑴
  switch (action.type){
    case 'INCREASE': return {count: state.count + 1};
    case 'DECREASE': return {count: state.count - 1};
    default: return state;
  }
}

const actions = {---------->⑵
  increase: () => ({type: 'INCREASE'}),
  decrease: () => ({type: 'DECREASE'})
}

const store = createStore(reducer);---------->⑶

store.subscribe(() =>
  console.log(store.getState())
);

store.dispatch(actions.increase()) // {count: 1}
store.dispatch(actions.increase()) // {count: 2}
store.dispatch(actions.increase()) // {count: 3}
複製代碼

本身畫了一張很是簡陋的流程圖,方便理解redux的工做流程redux

react-redux

剛開始就說了,若是把store直接集成到React應用的頂層props裏面,只要各個子組件能訪問到頂層props就好了,好比這樣:api

<頂層組件 store={store}>
  <App />
</頂層組件>
複製代碼

不就ok了嗎?這就是 react-redux。Redux 官方提供的 React 綁定庫。 具備高效且靈活的特性。promise

React Redux 將組件區分爲 容器組件 和 UI 組件

  1. 前者會處理邏輯
  2. 後者只負責顯示和交互,內部不處理邏輯,狀態徹底由外部掌控

兩個核心

  • Provider

    看我上邊那個代碼的頂層組件4個字。對,你沒有猜錯。這個頂級組件就是Provider,通常咱們都將頂層組件包裹在Provider組件之中,這樣的話,全部組件就均可以在react-redux的控制之下了,可是store必須做爲參數放到Provider組件中去bash

    <Provider store = {store}>
        <App />
    <Provider>
    複製代碼
    這個組件的目的是讓全部組件都可以訪問到Redux中的數據。 
    複製代碼
  • connect

    這個纔是react-redux中比較難的部分,咱們詳細解釋一下服務器

    首先,先記住下邊的這行代碼:

    connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    複製代碼

    mapStateToProps

    這個單詞翻譯過來就是把state映射到props中去 ,其實也就是把Redux中的數據映射到React中的props中去。

    舉個栗子:

const mapStateToProps = (state) => {
      return {
      	// prop : state.xxx  | 意思是將state中的某個數據映射到props中
        foo: state.bar
      }
    }
複製代碼

而後渲染的時候就可使用this.props.foo

class Foo extends Component {
    constructor(props){
        super(props);
    }
    render(){
        return(
        	// 這樣子渲染的其實就是state.bar的數據了
            <div>this.props.foo</div>
        )
    }
}
Foo = connect()(Foo);
export default Foo;
複製代碼

而後這樣就能夠完成渲染了

mapDispatchToProps

這個單詞翻譯過來就是就是把各類dispatch也變成了props讓你能夠直接使用

const mapDispatchToProps = (dispatch) => { // 默認傳遞參數就是dispatch
  return {
    onClick: () => {
      dispatch({
        type: 'increatment'
      });
    }
  };
}
複製代碼
class Foo extends Component {
    constructor(props){
        super(props);
    }
    render(){
        return(
        	
             <button onClick = {this.props.onClick}>點擊increase</button>
        )
    }
}
Foo = connect()(Foo);
export default Foo;
複製代碼

組件也就改爲了上邊這樣,能夠直接經過this.props.onClick,來調用dispatch,這樣子就不須要在代碼中來進行store.dispatch了

react-redux的基本介紹就到這裏了

redux-saga

若是按照原始的redux工做流程,當組件中產生一個action後會直接觸發reducer修改state,reducer又是一個純函數,也就是不能再reducer中進行異步操做;

而每每實際中,組件中發生的action後,在進入reducer以前須要完成一個異步任務,好比發送ajax請求後拿到數據後,再進入reducer,顯然原生的redux是不支持這種操做的

這個時候急需一箇中間件來處理這種業務場景,目前最優雅的處理方式天然就是redux-saga

核心講解

一、Saga 輔助函數

redux-saga提供了一些輔助函數,用來在一些特定的action 被髮起到Store時派生任務,下面我先來說解兩個輔助函數:takeEverytakeLatest

  • takeEvery

takeEvery就像一個流水線的洗碗工,過來一個髒盤子就直接執行後面的洗碗函數,一旦你請了這個洗碗工他會一直執行這個工做,絕對不會中止接盤子的監聽過程和觸發洗盤子函數

例如:每次點擊 按鈕去Fetch獲取數據時時,咱們發起一個 FETCH_REQUESTED 的 action。 咱們想經過啓動一個任務從服務器獲取一些數據,來處理這個action,相似於

window.addEventLister('xxx',fn)
複製代碼

當dispatch xxx的時候,就會執行fn方法,

首先咱們建立一個將執行異步 action 的任務(也就是上邊的fn):

// put:你就認爲put就等於 dispatch就能夠了;

// call:能夠理解爲實行一個異步函數,是阻塞型的,只有運行完後面的函數,纔會繼續往下;
// 在這裏能夠片面的理解爲async中的await!但寫法直觀多了!
import { call, put } from 'redux-saga/effects'

export function* fetchData(action) {
   try {
      const apiAjax = (params) => fetch(url, params);
      const data = yield call(apiAjax);
      yield put({type: "FETCH_SUCCEEDED", data});
   } catch (error) {
      yield put({type: "FETCH_FAILED", error});
   }
}
複製代碼

而後在每次 FETCH_REQUESTED action 被髮起時啓動上面的任務,也就至關於每次觸發一個名字爲 FETCH_REQUESTED 的action就會執行上邊的任務,代碼以下

import { takeEvery } from 'redux-saga'

function* watchFetchData() {

  yield* takeEvery("FETCH_REQUESTED", fetchData)
}
複製代碼

注意:上面的 takeEvery 函數可使用下面的寫法替換

function* watchFetchData() {
  
   while(true){
     yield take('FETCH_REQUESTED');
     yield fork(fetchData);
   }
}
複製代碼
  • takeLatest

在上面的例子中,takeEvery 容許多個 fetchData 實例同時啓動,在某個特定時刻,咱們能夠啓動一個新的 fetchData 任務, 儘管以前還有一個或多個 fetchData 還沒有結束

若是咱們只想獲得最新那個請求的響應(例如,始終顯示最新版本的數據),咱們可使用 takeLatest 輔助函數

import { takeLatest } from 'redux-saga'

function* watchFetchData() {
  yield* takeLatest('FETCH_REQUESTED', fetchData)
}
複製代碼

和takeEvery不一樣,在任什麼時候刻 takeLatest 只容許執行一個 fetchData 任務,而且這個任務是最後被啓動的那個,若是以前已經有一個任務在執行,那以前的這個任務會自動被取消

二、Effect Creators

redux-saga框架提供了不少建立effect的函數,下面咱們就來簡單的介紹下開發中最經常使用的幾種

  • take(pattern)
  • put(action)
  • call(fn, ...args)
  • fork(fn, ...args)
  • select(selector, ...args)

take(pattern)

take函數能夠理解爲監聽將來的action,它建立了一個命令對象,告訴middleware等待一個特定的action, Generator會暫停,直到一個與pattern匹配的action被髮起,纔會繼續執行下面的語句,也就是說,take是一個阻塞的 effect

用法:

function* watchFetchData() {
   while(true) {
   // 監聽一個type'FETCH_REQUESTED' 的action的執行,直到等到這個Action被觸發,纔會接着執行下面的 		yield fork(fetchData)  語句
     yield take('FETCH_REQUESTED');
     yield fork(fetchData);
   }
}
複製代碼

put(action)

put函數是用來發送action的 effect,你能夠簡單的把它理解成爲redux框架中的dispatch函數,當put一個action後,reducer中就會計算新的state並返回,注意: put 也是阻塞 effect

用法:

export function* toggleItemFlow() {
    let list = []
    // 發送一個type'UPDATE_DATA' 的Action,用來更新數據,參數爲 `data:list`
    yield put({
      type: actionTypes.UPDATE_DATA,
      data: list
    })
}
複製代碼

call(fn, ...args)

call函數你能夠把它簡單的理解爲就是能夠調用其餘函數的函數,它命令 middleware 來調用fn 函數, args爲函數的參數,注意: fn 函數能夠是一個 Generator 函數,也能夠是一個返回 Promise 的普通函數,call 函數也是阻塞 effect

用法:

export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

export function* removeItem() {
  try {
    // 這裏call 函數就調用了 delay 函數,delay 函數爲一個返回promise 的函數
    return yield call(delay, 500)
  } catch (err) {
    yield put({type: actionTypes.ERROR})
  }
}
複製代碼

fork(fn, ...args)

fork 函數和 call 函數很像,都是用來調用其餘函數的,可是fork函數是非阻塞函數,也就是說,程序執行完 yield fork(fn, args) 這一行代碼後,會當即接着執行下一行代碼語句,而不會等待fn函數返回結果後,在執行下面的語句

用法:

import { fork } from 'redux-saga/effects'

export default function* rootSaga() {
  // 下面的四個 Generator 函數會一次執行,不會阻塞執行
  yield fork(addItemFlow)
  yield fork(removeItemFlow)
  yield fork(toggleItemFlow)
  yield fork(modifyItem)
}
複製代碼

select(selector, ...args)

select 函數是用來指示 middleware調用提供的選擇器獲取Store上的state數據,你也能夠簡單的把它理解爲redux框架中獲取store上的 state數據同樣的功能store.getState()

用法:

export function* toggleItemFlow() {
     // 經過 select effect 來獲取 全局 state上的 `getTodoList` 中的 list
     let tempList = yield select(state => state.getTodoList.list)
}
複製代碼

一個具體的實例

**index.js **

import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga'

import rootSaga from './sagas'
import Counter from './Counter'
import rootReducer from './reducers'

const sagaMiddleware = createSagaMiddleware() // 建立了一個saga中間件實例

// 下邊這句話和下邊的兩行代碼建立store的方式是同樣的
// const store = createStore(reducers,applyMiddlecare(middlewares))

const createStoreWithMiddleware = applyMiddleware(middlewares)(createStore)
const store = createStoreWithMiddleware(rootReducer)

sagaMiddleware.run(rootSaga)

const action = type => store.dispatch({ type })

function render() {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={() => action('INCREMENT')}
      onDecrement={() => action('DECREMENT')}
      onIncrementAsync={() => action('INCREMENT_ASYNC')} />,
    document.getElementById('root')
  )
}

render()

store.subscribe(render)
複製代碼

sagas.js

import { put, call, take,fork } from 'redux-saga/effects';
import { takeEvery, takeLatest } from 'redux-saga'

export const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

function* incrementAsync() {
  // 延遲 1s 在執行 + 1操做
  yield call(delay, 1000);
  yield put({ type: 'INCREMENT' });
}

export default function* rootSaga() {
  // while(true){
  //   yield take('INCREMENT_ASYNC');
  //   yield fork(incrementAsync);
  // }

  // 下面的寫法與上面的寫法上等效
  yield* takeEvery("INCREMENT_ASYNC", incrementAsync)
}
複製代碼

reducer.js

export default function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    case 'INCREMENT_ASYNC':
      return state
    default:
      return state
  }
}
複製代碼

從上面的代碼結構能夠看出,redux-saga的使用方式仍是比較簡單的,相比較以前的redux框架的CounterApp,多了一個sagas的文件,reducers文件仍是以前的使用方式

redux-saga基本用法總結:

  1. 使用 createSagaMiddleware 方法建立 saga 的 Middleware ,而後在建立的 redux 的 store 時,使用 applyMiddleware 函數將建立的 saga Middleware 實例綁定到 store 上,最後能夠調用 saga Middleware 的 run 函數來執行某個或者某些 Middleware 。
  2. 在 saga 的 Middleware 中,可使用 takeEvery 或者 takeLatest 等 API 來監聽某個 action ,當某個 action 觸發後, saga 可使用 call 發起異步操做,操做完成後使用 put 函數觸發 action ,同步更新 state ,從而完成整個 State 的更新。

ok,故事到這裏就接近尾聲了,以上主要介紹了redux,react-redux和redux-saga目前redux全家桶主流的一些產品,接下來,主要會產出一下根據源碼,手寫一下redux和react-redux的輪子

但願各位大佬點贊,關注,鼓勵一下,不足之處,還望斧正~

相關文章
相關標籤/搜索