一、什么是saga?
saga react是官方推出的解决异步问题的库,作用类似于之前写过的thunk。可以参考看一下作用。
手册
二、saga初步了解
首先是了解一下目录结构:
test
|-node_modules
|-www
|-app
|-main.js
|-App.js
|-reducers
|-index.js
|-counterReducer.js
|-actions
|-counter.js
|-index.html
|-webpack.config.js
|-package.json
安装redux-saga
npm install –save dva
创建sagas.js的文件:
目录结构:
test
|-node_modules
|-www
|-app
|-main.js
|-App.js
|-reducers
|-index.js
|-counterReducer.js
|-actions
|-counter.js
|-sagas.js
|-index.html
|-webpack.config.js
|-package.json
这个文件现在向外暴露一个加星函数:
export function* helloSaga() {
console.log('你好,我是saga');
}
关于加星函数:
ES6中有一个新的特性,叫做产生器,就是加星函数,英语叫做Generator。
function* say(){
yield "你好";
yield "哈哈";
yield "嘻嘻";
yield "么么哒";
}
var h = say(); //得到产生器的实例
console.log(h.next()); //得到第1个产生的内容。 你好
console.log(h.next()); //得到第2个产生的内容。 哈哈
console.log(h.next()); //得到第3个产生的内容。 嘻嘻
console.log(h.next()); //得到第4个产生的内容。 么么哒
也就是说,一个函数如果加上了*,此时就是一个产生器,里面就要有一条一条的yield语句。
此时我们可以
var h = say();
得到产生器的实例。这个实例就是h.next()逐条调用yield的返回值。
h.next();
h.next();
h.next();
能够有多条return的函数,就是产生器。
yield后面假如是一个返回Promise对象的函数,自动有async、await的能力,自动停留等待。
箭头函数不能加*,必须是function。
改变main.js:
import React from "react";
import ReactDOM from "react-dom";
import {createStore , applyMiddleware} from "redux";
import {Provider} from "react-redux";
// 引入redux-saga
import createSagaMiddleware from "redux-saga";
import App from "./App.js";
import reducer from "./reducers";
//引入默认saga
import { saga } from './sagas.js';
// 创建saga的中间件
const sagaMiddleware = createSagaMiddleware();
// 仓库
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
//运行默认saga
sagaMiddleware.run(saga);
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>
,
document.getElementById("app")
);
然后补充一个babel的plugin(因为加星函数需要):
npm install –save-dev babel-plugin-transform-runtime
改变webpack.config.js文件:
const path = require('path');
module.exports = {
entry: "./www/app/main.js",
output: {
path: path.resolve(__dirname, "www/dist"),
filename: "bundle.js",
publicPath: "/xuni/"
},
mode : "development",
module: {
rules: [
{
test: /\.js$/,
include: [
path.resolve(__dirname, "www/app")
],
exclude: [
path.resolve(__dirname, "node_modules")
],
loader: "babel-loader",
options: {
presets: ["es2015","react"],
plugins: ["transform-object-rest-spread","transform-runtime"] // 这里引入transform-runtime
}
}
]
},
devServer: {
proxy: {
//下面的参数从vue-cli上扒拉出来的
"/api": {
target: 'http://127.0.0.1:3000/',
changeOrigin: true,
pathRewrite: {
'^/api': '/'
}
}
}
}
}
此时输出项目可见:
三、异步与拦截
我们现在给按钮“按我加服务器那么多”绑定事件,让这个按钮能够发出一个普通的action。
counter.js:
export const add = () => ({"type" : "ADD"});
export const minus = () => ({"type" : "MINUS"});
注意这个,我将在sagas.js中对这个指令进行拦截
export const addServer = () => ({"type" : "ADDSERVER"});
改变sagas.js文件:
import {all , takeEvery} from "redux-saga/effects"
按照第一二三顺序查看
********************************************************
//第三步:
//worker saga,工人saga。
//这个saga的功能是被拦截之后做的事情。
function* addServer(){
//做异步
const {a} = yield fetch("/api/api").then(data=>data.json());
//put用于发出新的action
yield put({type : "ADD" , a});
// if语句
const { a } = yield select(state => state.counter); //这是获取到当前的state值
//判断是不是奇数
if(a % 2 == 1){
//发出新的action
yield put({ type: "ADD" });
}
}
********************************************************
// 第二步:
//watcher saga,监控saga。
//这个saga的名字叫做watchAddServer,暗示了我们它在拦截ADDSERVER这个action,我要在这里进行拦截了
function* watchAddServer(){
//takeEvery是英语的“拦截”的意思,这里表示拦截所有的ADDSERVER的action,一旦拦截到,做addServer函数。
// 拦截成功执行addServer就是第三步里的函数
yield takeEvery('ADDSERVER', addServer);
}
********************************************************
//第一步:
//默认暴露的函数,这个函数被main.js中的run调用了。
export default function* () {
//all表示“并行执行”的意思,此时现在执行了watchAddServer这个函数。就是第二步里头的函数。
yield all([watchAddServer()]);
}
- 什么是saga,一系列有监控、拦截、工作函数组成的“拦截体系”我们称为saga。
- 小结一下:
就是在action中我们都以同步的形式发送dispatch 然而这个请求要进行异步操作的话
我们将在sagas.js中对她进行拦截 拦截成功后发出一个函数,在这个函数里进行异步操作发送新的action
发送的新的action才是reducer中接收到的 - 与thunk对比
在thunk中我们在actions中将异步用()=>()=>{}的形式,而在saga中actions中都是同步形式
saga中所有的异步都转移到了sagas.js文件中,结构要清晰了很多 - sagas.js文件,大约有三部分组成:
默认暴露的:all()
watcher saga: takeEvery()
worker saga : fecth() 、put() - saga的核心思路是:类似你的项目中的一个单独的线程,单独负责副作用。
三、辅助函数
redux-saga提供了一些“辅助函数”来帮我们监听一些被指定的发往store的action。
takeEvery:监听action,执行函数
yield takeEvery("ADDSERVER" , addServer);
all:同步运行一些监听
yield all([watchAddServer() , watchJibianoububian()])
put:发action的
yield put({"type" : "ADD" , a})
call : 调用一个函数
import { all, takeEvery , put , select , call} from "redux-saga/effects" async function loadApi(){ //做异步 const { a } = await fetch("/api/api").then(data => data.json()); return a; } //worker saga,工人saga。 //这个saga的功能是被拦截之后做的事情。 function* addServer() { //发出新的action const a = yield call(loadApi); yield put({type : "ADD" , a}); } …… ……
fork:也是调用函数,但是要在调用的函数中加上put,而没有return值。
import { all, takeEvery , put , select , call , fork} from "redux-saga/effects" function* loadApi(){ //做异步 const { a } = yield fetch("/api/api").then(data => data.json()); yield put({"type" : "ADD" , a}) } //worker saga,工人saga。 //这个saga的功能是被拦截之后做的事情。 function* addServer() { //发出新的action yield fork(loadApi) }
四、saga有三个好处
1)saga写异步的地方就是saga文件,而不涉及action文件,action文件中画风都是一个(),都是非常简单的action creator。靠拦截的思路去写saga。
2)副作用清晰,比如做分页项目,改变排序的时候,一定会分页要到第1页,此时saga中用两个yield put()轻松解决。
3)数据的集结感非常强,做条件查询的业务非常适合。saga会集结当前reducer中的查询条件,包括按什么排序、排序方向、页码信息等,集结完毕,出发进行查询,返回的results经过put,当做载荷,改变reducer影响视图。
五、 拆分一下
类似reducers 和 actions 的拆分:
创建sagas文件夹,并创建counterSagas.js
目录结构:
test
|-node_modules
|-www
|-app
|-main.js
|-App.js
|-reducers
|-index.js
|-counterReducer.js
|-actions
|-counter.js
|-sagas.js
|-sagas
|-counterSagas.js
|-index.html
|-webpack.config.js
|-package.json
将all 的部分放在sagas.js中:
import {all} from "redux-saga/effects";
引入conterSagas.js中的watcher saga函数
import {watchAddServer, watchAddServer2, watchJibianoububian} from "./sagas/counterSagas.js";
//默认暴露的函数,这个函数被main.js中的run调用了。
export default function* () {
//all表示“并行执行”的意思,此时现在执行了watchAddServer这个函数。
yield all([
watchAddServer(),
watchAddServer2(),
watchJibianoububian()
]);
}
sagas/conterSagas.js如下:
import { all, takeEvery, put, select, call, fork } from "redux-saga/effects"
//worker saga,工人saga。
//这个saga的功能是被拦截之后做的事情。
function* addServer() {
//做异步
const { a } = yield fetch("/api/api").then(data => data.json());
yield put({ "type": "ADD", a })
}
function* addServer2() {
//做异步
const { a } = yield fetch("/api/api2").then(data => data.json());
//发出新的action
yield put({ type: "ADD", a });
}
function* jibianoububian() {
const { v } = yield select(state => state.counterReducer);
//判断是不是奇数
if (v % 2 == 1) {
//发出新的action
yield put({ type: "ADD" });
}
}
//*************************************************************** */
//watcher saga,监控saga。
//这个saga的名字叫做watchAddServer,暗示了我们它在拦截ADDSERVER这个action
export const watchAddServer = function* () {
//takeEvery是英语的“拦截”的意思,这里表示拦截所有的ADDSERVER的action,一旦拦截到,做addServer函数。
yield takeEvery('ADDSERVER', addServer);
}
export const watchAddServer2 = function* () {
//takeEvery是英语的“拦截”的意思,这里表示拦截所有的ADDSERVER2的action,一旦拦截到,做addServer2函数。
yield takeEvery('ADDSERVER2', addServer2);
}
export const watchJibianoububian = function* () {
//takeEvery是英语的“拦截”的意思,这里表示拦截所有的JIBIANOUBUBIAN的action,一旦拦截到,做jibianoububian函数。
yield takeEvery('JIBIANOUBUBIAN', jibianoububian);
}