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());
2.1.1 進一步分析
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
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/
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>
<button onClick={() => this.props.increateAsync()}>異步新增</button>
<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