redux-saga

1 概述

redux-saga 是 redux 一個中間件芳绩,用于解決異步問題极舔。

2 es6 Generator

解決地獄回調(diào)問題涵亏,通過 yield 關(guān)鍵字镊屎,可以讓函數(shù)的執(zhí)行流掛起惹挟。

使用在線工具驗證:https://jsbin.com/musedihito/edit?js,console

參考:
https://blog.csdn.net/tcy83/article/details/80427195

2.1 基礎(chǔ)示例

function* geo() { // 聲明一個 Generator 函數(shù)
  const x = yield 'xxx';
  console.log(x); // 返回的是 undefined
  const y = yield 'yyy'
}
const Geo = geo(); // 調(diào)用,返回 Generator 對象賦值給 Geo
console.log(Geo.next()); // 通過 next() 方法缝驳,執(zhí)行Generator 中的函數(shù)體连锯。
console.log(Geo.next());
console.log(Geo.next());


結(jié)果

2.1.1 進一步分析

yield和next方法
例子1
例子2
function* geo() {
  const post = yield $.getJSON("https://jsonplaceholder.typicode.com/posts");
  console.log(post[0].title);
  const users = yield $.getJSON("https://jsonplaceholder.typicode.com/users");
  console.log(users[0]);
  
}

run(geo);

function run(geo) {
  const Geo = geo(); 
  
  function handle(yielded) {
    if(!yielded.done) {
      yielded.value.then(function(data){
        return handle(Geo.next(data));
      })
    }
  }
  
  handle(Geo.next());
  
}


2.3 區(qū)別 yield* 和 yield

區(qū)別yield和yield*

4 簡單理解 'redux-saga/effects' 中的幾個關(guān)鍵字:fork归苍,call, put运怖,takeEvery霜医,takeLatest,all

4.1 fork

參考:https://redux-saga-in-chinese.js.org/docs/advanced/ForkModel.html

創(chuàng)建一個新的進程或者線程驳规,并發(fā)發(fā)送請求。

關(guān)鍵代碼:

function* user() {
  yield takeEvery('FETCH_REQUEST', fetch_user); // 監(jiān)聽 FETCH_REQUEST action
}

// 并發(fā)發(fā)送請求
function* fetch_user() {
  const [users, todos] = [
    yield fork(fetchResource, 'https://jsonplaceholder.typicode.com/users'),
    yield fork(fetchResource, 'https://jsonplaceholder.typicode.com/todos')
  ]
}


function* fetchResource(resource) {
  const data = yield call(axios.get, resource);
  
  // 獲取 call 數(shù)據(jù)署海,觸發(fā)成功后的 action
  yield put({ type: 'FETCH_SUCESS', uu: data });
  
}

4.2 call

發(fā)送 api 請求

4.3 put

發(fā)送對應(yīng)的 dispatch吗购,觸發(fā)對應(yīng)的 action

4.4 takeEvery

  • 監(jiān)聽對應(yīng)的 action;
  • 每一次 dispatch 都會觸發(fā)砸狞;例如:點擊一個新增的按鈕捻勉,2s 后觸發(fā)新增動作,在2s內(nèi)不斷點擊按鈕刀森,這時候踱启,每一次點擊,都是有效的研底。

yield takeEvery('FETCH_USER', fetch_user);

4.5 takeLatest

  • 監(jiān)聽對應(yīng)的 action埠偿;
  • 只會觸發(fā)最后一次 dispatch;例如:點擊一個新增的按鈕榜晦,2s 后觸發(fā)新增動作冠蒋,在2s內(nèi)不斷點擊按鈕,這時候乾胶,只有最后一次點擊是有效的抖剿。

yield takeLatest('FETCH_USER', fetch_user);

4.6 all

跟 fork 一樣,同時并發(fā)多個 action识窿,沒有順序斩郎。

yield all([ // 同時并發(fā)多個
  ...rootUser,
  add()
])

5 demo 示例

使用 create-react-app 腳手架初始化;
在線請求API:https://jsonplaceholder.typicode.com/

目錄結(jié)構(gòu)

src/index.js:項目入口文件配置 redux 和 saga

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

import { createStore, applyMiddleware } from 'redux'; // 中間件和 store
import { Provider } from 'react-redux'; // provider
import { composeWithDevTools } from 'redux-devtools-extension'; // 調(diào)試工具
import rootReducer from './reducers'; // reducers

import createSagaMiddleware from 'redux-saga'; // 1:saga 引入createSagaMiddleware
import rootSaga from './sagas'; // 5:自己寫的根 rootSaga


// 2:創(chuàng)建saga中間件
const sagaMiddleware = createSagaMiddleware()

const store = createStore(
  rootReducer,
  composeWithDevTools( // 3:把 sagaMiddleware 當做一個中間件喻频,引用調(diào)試工具
    applyMiddleware(sagaMiddleware)
  )
)

// 4:啟動 saga
sagaMiddleware.run(rootSaga);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

serviceWorker.unregister();


src/App.js:頁面

import React, { Component } from 'react';
import './App.css';
import { connect } from 'react-redux';
import { increate, increateAsync, fetch_user } from './actions/counter';

class App extends Component {

  render() {
    const { isFetch, error, user } = this.props.users;
    let data = "";
    if (isFetch) {
      data = '正在加載中缩宜。。半抱。'
    } else if (user) {
      data = user.data[0]['name'];
    } else if (error) {
      data = error.message;
    }
    return (
      <div className="App">
        {/* 觸發(fā)dispatch脓恕,發(fā)送對應(yīng)的action */}
        <div style={{ marginBottom: 20 }}>
          <p>{this.props.counter}</p>
          <button onClick={() => this.props.increate()}>新增</button>
          &nbsp;&nbsp;&nbsp;
          <button onClick={() => this.props.increateAsync()}>異步新增</button>
          &nbsp;&nbsp;&nbsp;
          <button onClick={() => this.props.fetch_user()}>axios請求</button>
        </div>
        <h2>{data}</h2>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    counter: state.counter, // state 對應(yīng)的 key, 在 reducers/index.js 中聲明。
    users: state.us
  }
}

// mapStateToProps窿侈,在 reducers/index.js 中炼幔,通過 connect 導入對應(yīng)的 state
// { increate, increateAsync, fetch_user } ,通過 connect 導入對應(yīng)的action,在view觸發(fā)相應(yīng)的action
export default connect(mapStateToProps, { increate, increateAsync, fetch_user })(App);



constants/index.js:常量

export const INCREMENT = "INCREMENT";
export const INCREMENT_ASYNC = "INCREMENT_ASYNC";


actions/counter.js:action

import { INCREMENT, INCREMENT_ASYNC } from '../constants';

export const increate = () => {
  return {
    type: INCREMENT
  }
}

export const increateAsync = () => {
  return {
    type: INCREMENT_ASYNC
  }
}


export const fetch_user = () => {
  return {
    type: 'FETCH_REQUEST'
    // type: 'FETCH_USER'
  }
}



reducers/index.js:reducers 的入口文件

import { combineReducers } from 'redux';
import us from './users';
import counter from './counter';

export default combineReducers({
  us,
  counter
})

reducers/counter.js:

import { INCREMENT } from '../constants';

const counter = (state = 1, action = {}) => {
  switch (action.type) {
    case INCREMENT:
      return state + 1;
    default: return state
  }
}

export default counter;


reducers/users.js:

const initialState = {
  isFetch: false,
  error: null,
  user: null
}

const u = (state = initialState, action = {}) => {
  switch (action.type) {
    case 'FETCH_REQUEST':
      return state = {
        isFetch: true,
        error: null,
        user: null
      };
    case 'FETCH_SUCESS':
      return state = {
        isFetch: false,
        error: null,
        user: action.uu
      };
    case 'FETCH_FAIL':
      return state = {
        isFetch: false,
        error: action.errors,
        user: null
      };
    default: return state
  }
}

export default u;

sagas/index.js:sagas 的入口文件

import { all, fork } from 'redux-saga/effects';
import rootUser from './fetchUser';
import { add } from './counter';

export default function* rootSaga() {
  yield all([ // 同時并發(fā)多個
    ...rootUser,
    add()
  ])
}


sagas/counter.js:

import { INCREMENT_ASYNC, INCREMENT } from '../constants';
import { delay } from 'redux-saga';
import { call, put, takeEvery} from 'redux-saga/effects';

function* increase() {
  yield call(delay, 1000); // 需要執(zhí)行異步的時候史简,直接調(diào)用 call
  yield put({ type: INCREMENT });
}

// 直接 export 函數(shù)乃秀,沒有做整理
export function* add() {
  yield takeEvery(INCREMENT_ASYNC, increase);
}


sagas/fetchUser.js:

使用數(shù)組導出函數(shù)肛著,就不用一個一個函數(shù)導出,優(yōu)化了代碼跺讯!

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

function* fetch_user() {
  try {
    const users = yield call(axios.get, 'https://jsonplaceholder.typicode.com/users'); // axios.get('https://jsonplaceholder.typicode.com/users')      
    yield put({ type: 'FETCH_SUCESS', uu: users });
  } catch (e) {
    yield put({ type: 'FETCH_FAIL', errors: e });
  }
}

function* fetch_todo() {
  const todos = yield call(axios.get, 'https://jsonplaceholder.typicode.com/todos'); // axios.get('https://jsonplaceholder.typicode.com/users')      
  console.log(todos);
}

function* user() {
  yield takeEvery('FETCH_REQUEST', fetch_user); // 正在加載數(shù)據(jù)
}

function* todo() {
  yield takeEvery('FETCH_TODO', fetch_todo);
}

// 使用數(shù)組導出
const rootUser = [
  user(),
  todo()
]

export default rootUser;


package.json:

{
  "name": "redux-saga-demo",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^0.18.0",
    "react": "^16.6.0",
    "react-dom": "^16.6.0",
    "react-redux": "^5.1.0",
    "react-scripts": "2.1.0",
    "redux": "^4.0.1",
    "redux-devtools-extension": "^2.13.5",
    "redux-saga": "^0.16.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}


效果

6 總結(jié)體會

1:使用同步的操作枢贿,處理異步的請求;
2:使用 redux + redux-saga刀脏,在入口文件 index.js 配置 saga局荚;
3:在 saga 中,使用 takeEvery 或者 takeLatest 在項目啟動的時候愈污,監(jiān)聽對應(yīng)的 action耀态,觸發(fā)對應(yīng)的 action;
4:當頁面觸發(fā)了對應(yīng)的 action 時暂雹,除了會去尋找對應(yīng)的 reducer(找不到也沒事)首装,進行操作;也會觸發(fā) saga 監(jiān)聽的 action杭跪,進行異步請求等操作仙逻;

代碼:
https://github.com/hongzelin/redux-saga-demo

7 參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涧尿,隨后出現(xiàn)的幾起案子系奉,更是在濱河造成了極大的恐慌,老刑警劉巖姑廉,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喜最,死亡現(xiàn)場離奇詭異,居然都是意外死亡庄蹋,警方通過查閱死者的電腦和手機瞬内,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來限书,“玉大人虫蝶,你說我怎么就攤上這事【胛鳎” “怎么了能真?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扰柠。 經(jīng)常有香客問我粉铐,道長,這世上最難降的妖魔是什么卤档? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任蝙泼,我火速辦了婚禮,結(jié)果婚禮上劝枣,老公的妹妹穿的比我還像新娘汤踏。我一直安慰自己织鲸,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布溪胶。 她就那樣靜靜地躺著搂擦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哗脖。 梳的紋絲不亂的頭發(fā)上瀑踢,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音才避,去河邊找鬼丘损。 笑死,一個胖子當著我的面吹牛工扎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播衔蹲,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼肢娘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舆驶?” 一聲冷哼從身側(cè)響起橱健,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沙廉,沒想到半個月后拘荡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡撬陵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年珊皿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巨税。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡蟋定,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出草添,到底是詐尸還是另有隱情驶兜,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布远寸,位于F島的核電站抄淑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏驰后。R本人自食惡果不足惜肆资,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灶芝。 院中可真熱鬧迅耘,春花似錦贱枣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至栖秕,卻和暖如春春塌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背簇捍。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工只壳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人暑塑。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓吼句,卻偏偏與公主長得像,于是被迫代替她去往敵國和親事格。 傳聞我的和親對象是個殘疾皇子惕艳,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

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