1 概述
redux-saga 是 redux 一个中间件,用于解决异步问题。
2 es6 Generator
解决地狱回调问题,通过 yield 关键字,可以让函数的执行流挂起。
使用在线工具验证:https://jsbin.com/musedihito/edit?js,console
参考:
https://blog.csdn.net/tcy83/article/details/80427195
2.1 基础示例
function* geo() { // 声明一个 Generator 函数
const x = yield 'xxx';
console.log(x); // 返回的是 undefined
const y = yield 'yyy'
}
const Geo = geo(); // 调用,返回 Generator 对象赋值给 Geo
console.log(Geo.next()); // 通过 next() 方法,执行Generator 中的函数体。
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 区别 yield* 和 yield
4 简单理解 'redux-saga/effects' 中的几个关键字:fork,call, put,takeEvery,takeLatest,all
4.1 fork
参考:https://redux-saga-in-chinese.js.org/docs/advanced/ForkModel.html
创建一个新的进程或者线程,并发发送请求。
关键代码:
function* user() {
yield takeEvery('FETCH_REQUEST', fetch_user); // 监听 FETCH_REQUEST action
}
// 并发发送请求
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 数据,触发成功后的 action
yield put({ type: 'FETCH_SUCESS', uu: data });
}
4.2 call
发送 api 请求
4.3 put
发送对应的 dispatch,触发对应的 action
4.4 takeEvery
- 监听对应的 action;
- 每一次 dispatch 都会触发;例如:点击一个新增的按钮,2s 后触发新增动作,在2s内不断点击按钮,这时候,每一次点击,都是有效的。
yield takeEvery('FETCH_USER', fetch_user);
4.5 takeLatest
- 监听对应的 action;
- 只会触发最后一次 dispatch;例如:点击一个新增的按钮,2s 后触发新增动作,在2s内不断点击按钮,这时候,只有最后一次点击是有效的。
yield takeLatest('FETCH_USER', fetch_user);
4.6 all
跟 fork 一样,同时并发多个 action,没有顺序。
yield all([ // 同时并发多个
...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'; // 调试工具
import rootReducer from './reducers'; // reducers
import createSagaMiddleware from 'redux-saga'; // 1:saga 引入createSagaMiddleware
import rootSaga from './sagas'; // 5:自己写的根 rootSaga
// 2:创建saga中间件
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
rootReducer,
composeWithDevTools( // 3:把 sagaMiddleware 当做一个中间件,引用调试工具
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">
{/* 触发dispatch,发送对应的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 对应的 key, 在 reducers/index.js 中声明。
users: state.us
}
}
// mapStateToProps,在 reducers/index.js 中,通过 connect 导入对应的 state
// { increate, increateAsync, fetch_user } ,通过 connect 导入对应的action,在view触发相应的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([ // 同时并发多个
...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); // 需要执行异步的时候,直接调用 call
yield put({ type: INCREMENT });
}
// 直接 export 函数,没有做整理
export function* add() {
yield takeEvery(INCREMENT_ASYNC, increase);
}
sagas/fetchUser.js:
使用数组导出函数,就不用一个一个函数导出,优化了代码!
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); // 正在加载数据
}
function* todo() {
yield takeEvery('FETCH_TODO', fetch_todo);
}
// 使用数组导出
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 总结体会
1:使用同步的操作,处理异步的请求;
2:使用 redux + redux-saga,在入口文件 index.js 配置 saga;
3:在 saga 中,使用 takeEvery 或者 takeLatest 在项目启动的时候,监听对应的 action,触发对应的 action;
4:当页面触发了对应的 action 时,除了会去寻找对应的 reducer(找不到也没事),进行操作;也会触发 saga 监听的 action,进行异步请求等操作;
代码:
https://github.com/hongzelin/redux-saga-demo