一、redux-saga解决异步

redux-thunk 和 redux-saga

使用redux它们是必选的,二选一,它们两个都可以很好的实现一些复杂情况下redux,本质都是为了解决异步action而生,使redux保持完整性,不至于太过混乱。redux-saga 是一个用于管理Redux 应用异步操作的中间件。 redux-saga 通过创建 Sagas将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件。而且提供了takeLatest/takeEvery可以对事件的仅关注最近事件、关注每一次、事件限频;reudx-saga更方便测试,等等太多了。

npm install --save redux-saga

https://redux-saga.js.org/

https://redux-saga-in-chinese.js.org/

新手教学:

https://redux-saga-in-chinese.js.org/docs/introduction/BeginnerTutorial.html

saga就是拦截action,进行异步请求,转发action,再去reducer进行处理请求

在app根目录创建sagas.js文件,和main.js同级

// ES6的新特性,叫做Generator,产生器
// 是一种可以被调用的时候加next()打断点的特殊函数
export function* helloSaga() {
console.log('Hello Sagas!');
}

示例代码

main.js:

import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware} from "redux";
import {Provider} from "react-redux";
import createSagaMiddleware from 'redux-saga'; import App from "./containers/App.js";
import reducer from "./reducers";
//引入sages文件
import { helloSaga } from './sagas.js' //创建saga中间件
const sagaMiddleware = createSagaMiddleware(helloSaga)
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
//运行
sagaMiddleware.run(helloSaga) ReactDOM.render(
<Provider store={store}>
<App></App>
</Provider>,
document.getElementById("app")
);

components/counter/index.js写一个按钮:

<button onClick={()=>{this.props.counterActions.addServer()}}>加服务器数据</button>

actions/counterActions.js文件中发出一个action叫ADDSERVER,但异步还没发出:

import { ADD, MINUS, ADDSERVER } from "../constants/COUNTER.js";
export const add = () => ({"type" : ADD});
export const minus = () => ({"type" : MINUS});
export const addServer = () => ({"type" : ADDSERVER});

constants/COUNTER.js

export const ADDSERVER = "ADDSERVER" 

改变saga.js:saga开始工作,它要开始劫持监听ADDSERVER

import { delay } from 'redux-saga'
import { put, takeEvery , all} from 'redux-saga/effects' //worker Saga将执行异步的ADDSERVER任务
function* addServerAsync(){
alert("我是工作saga")
} //watcher Saga
创建了一个 Saga watchAddServer。用了redux-saga提供的辅助函数takeEvery,
用于监听所有的 ADDSERVER action,并在 action 被匹配时执行addServerAsync任务。
function* watchAddServer(){
//takeEvery表示“使用每一个”,当有action的type是ADDSERVER时,就执addServerAsync函数
yield takeEvery('ADDSERVER', addServerAsync);
} //向外暴露一个默认的rootSaga,有一个设置的监听队列
export default function* rootSaga(){
//创建一系列的监听队列
yield all([watchAddServer()])
}

改变main.js:

import React from "react";
import ReactDOM from "react-dom";
import {createStore , applyMiddleware} from "redux";
import {Provider} from "react-redux";
import logger from "redux-logger";
import createSagaMiddleware from 'redux-saga'; import reducer from "./reducers/index.js";
import App from "./containers/App.js";
//引入sagas文件
import rootSaga from './sagas.js';
//创建saga中间件
const sagaMiddleware = createSagaMiddleware(); const store = createStore(reducer, applyMiddleware(logger, sagaMiddleware));
sagaMiddleware.run(rootSaga); ReactDOM.render(
<Provider store={store}>
<App></App>
</Provider>
,
document.getElementById("app")
);

sagas.js 请求服务器的具体语句,写在worker saga中。

function* addServerAsync() {
//拉取数据
const {result} = yield fetch("/api").then(data=>data.json());
//put就是发出(转发)action,action的type为了避讳,加_SYNC后缀
yield put({ "type": "ADDSERVER_SYNC" , result});
}

reducers/counter.js

export default (state = {"v" : 0} , action) => {
if(action.type == "ADD"){
...
}else if(action.type == "MINUS"){
...
}else if(action.type == "ADDSERVER_SYNC"){
return {
"v": state.v + action.result
}
}
return state;
}

saga就是拦截action,进行异步请求,转发action,再去reducer进行处理请求

 删除actions、constants文件夹,然后给reducers/counter.js的type都加上引号!

改counter/index.js,不用bindActionCreators了,直接发出action。

import React from 'react';
import {connect} from "react-redux";
class Counter extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Counter : {this.props.v}</h1>
<button onClick={()=>{this.props.add()}}>增加</button>
<button onClick={()=>{this.props.minus()}}>减少</button>
<button onClick={()=>{this.props.addServer()}}>加服务器数据</button>
</div>
);
}
}
export default connect(
({counter}) => ({
v : counter.v
}),
(dispatch) => ({
add(){
dispatch({"type" : "ADD"});
},
minus() {
dispatch({"type": "MINUS" });
},
addServer() {
dispatch({"type": "ADDSERVER" });
}
})
)(Counter);

加入pie图表组件

components/pie/index.js组件,记得在App.js引入组件

import React from 'react';
import {connect} from "react-redux"; class Pie extends React.Component {
constructor(props) {
super(props);
//请求数据
props.loadServerData();
} //组件已经上树
componentDidMount(){
//echarts是引入的百度的包提供的全局变量
this.pic = echarts.init(this.refs.pic);
}
componentWillUpdate(nextProps){
var option = {
...
};
//设置option组件就能显示了!
this.pic.setOption(option);
} render() {
return (
<div>
<h1>我是pie组件!</h1>
<div ref="pic" style={{"width":"300px" ,"height":"300px"}}></div>
<button onClick={()=>{this.props.toupiao('a')}}>清晰</button>
<button onClick={()=>{this.props.toupiao('b')}}>一般</button>
<button onClick={()=>{this.props.toupiao('c')}}>懵逼</button>
</div>
);
}
} export default connect(
({pie})=>({
result: pie.result
}),
(dispatch)=>({
loadServerData(){
dispatch({ "type": "LOADSERVERDATA"})
},
toupiao(zimu){
dispatch({ "type": "TOUPIAO" , zimu})
}
})
)(Pie);

reducers/pie.js,在reducers/index.js中引入

export default (state = {"result" : []} , action) => {
if (action.type == "LOADSERVERDATA_SYNC"){
return {
...state ,
result : action.result
}
}
return state;
}

sagas.js

import { delay } from 'redux-saga';
import { put, takeEvery , all} from 'redux-saga/effects'; //worker Saga
function* addServer() {
//拉取数据,转发action
const {result} = yield fetch("/api").then(data=>data.json());
yield put({ "type": "ADDSERVER_SYNC" , result});
} function* loadServerData() {
const {result} = yield fetch("/api2").then(data=>data.json());
yield put({ "type": "LOADSERVERDATA_SYNC", result });
} function* toupiao(action) {
const {result} = yield fetch("/toupiao/" + action.zimu).then(data=>data.json());
yield put({ "type": "LOADSERVERDATA_SYNC", result });
} // watcher Saga
function* watchAddServer() {
yield takeEvery('ADDSERVER', addServer);
} function* watchLoadServerData() {
yield takeEvery('LOADSERVERDATA', loadServerData);
} function* watchToupiao() {
yield takeEvery('TOUPIAO', toupiao);
} //向外暴露一个默认的rootSaga,有一个all设置的监听队列
export default function* rootSaga(){
//创建一系列的监听队列
yield all([watchAddServer(), watchLoadServerData() , watchToupiao()])
}

二、Dva简介和配置

2.1 Dva简介

文档:https://github.com/dvajs/dva/blob/master/README_zh-CN.md

React繁文缛节很多,为什么?

因为React将注意力放到了组件开发上,可以用class App extends React.Component类,它就是一个组件了,可以被任意插拔,<App></App>。

可被预测状态容器Redux感觉在React“之外”,不“浑然一体”。

比如要配置Redux:

……
var {createStore} from "redux";
……
const store = createStore(reducer);
……
<Provider store={store}>
<App></App>
</Provider>
组件中:
connect()(App);

示例代码

如果要使用异步,更感觉是在React“之外”,不“浑然一体”。

……
var {createStore} from "redux";
……
const store = createStore(reducer , applyMiddleware(saga));
run(rootSaga);
……
<Provider store={store}>
<App></App>
</Provider>

示例代码

这是因为React将注意力放到了组件开发上。

Vue框架要比React好很多,浑然一体方面简直无敌。

Vue天生带有Vuex,天生就可以有可被预测状态容器,有异步解决的方案。

阿里巴巴的云谦,发明了dvajs这个库,“集大成”者,本质的目的就是让程序“浑然一体”,一方面是方便起步,更大的发明利于团队组件开发,不用频繁在组件文件actions、saga、reducers文件之间进行切换了。


2.2 Hello World

npm install --save dva

dva中集成了redux、react-redux、redux-saga、react-router-redux,所以我们的项目装dva之前,要去掉这4个依赖。

注意:现在的package.js文件没有以上这些依赖:

{
"name": "react_study",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1"
},
"dependencies": {
"dva": "^2.4.0",
"express": "^4.16.3",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"redux-logger": "^3.0.6"
}
}

后端app.js提供静态化路由:

var express = require("express");
var app = express();
app.use(express.static("www")) app.get("/api",(req,res)=>{
res.json({"result":10})
}) app.listen(3000);

示例代码

main.js

import dva from 'dva';
//引入路由
import router from './router.js'; //创建app应用
const app = dva(); //注册视图,创建路由
app.router(router) //启动应用 上树运行
app.start('#app');

示例代码

router.js是路由,单页面应用的hash路由,router.js文件暂时不写任何路由功能,只返回一个标签。

import React from 'react';
export default ()=>{
return <h1>我是dva</h1>
}


2.3使用组件

组件没有任何的简化,还是原来的写法:

components/App.js

import React from 'react'
export default class App extends React.Component {
constructor(){
super()
}
render() {
return <div>
<h1>我是App组件</h1>
</div>
}
}

示例代码

在router.js(路由文件)引入App组件,表示一上来就挂载App组件:

import React from 'react';
import App from './components/App';
export default ()=>{
return <App></App>
}

示例代码


2.4使用redux,创建model,组件要问天要数据

这里非常大的改变,照着Vuex改的

注意:不是创建reducers文件夹,而是创建models文件夹,创建counter.js

export default {
"namespace":"counter", //命名空间
"state":{
a : 100
}
}

示例代码

内部,dva会自动将counter作为一个reducer的名字,进行combineReducers

main.js

import React from 'react';
import dva from 'dva';
// 引入路由
import router from './router.js';
import counter from './model/counter.js'; //创建app
const app = dva(); //创建路由
app.router(router)
//创建并使用全局数据
app.model(counter) //上树运行
app.start('#app');

组件没有任何变化,只不过connect要从dva中引包。


2.5改变天上数据

dispatch()没有改变

model文件counter.js创建一个属性叫reducers,里面放一个add函数,依然是纯函数,不能改变state,只能返回新的state。

export default {
"namespace" : "counter",
"state" : {
"a" : 100
},
"reducers" : {
add(state,action){
return {
"a" : state.a + action.n
}
}
}
}


2.6异步

改变model中counter.js文件,刚才已经有了reducers函数,那里是写同步的函数。

现在要创建一个effects函数,这里写异步的函数,很像vuex!!

export default {
"namespace" : "counter" ,
"state" : {
"a" : 100
},
"reducers" : {
//这里写同步函数
add(state , action){
return { "a": state.a + action.n}
}
},
"effects" : {
//这里写异步函数
*addServer(action , {put}){
const {result} = yield fetch("/api").then(data=>data.json());
//异步终点一定是某个同步函数,所以put重新触发某个reducers中的同步函数
//带着载荷参数,去改变state的数据
yield put({"type" : "add" , "n" : result})
}
}
}

组件的按钮没有变化:


2.7使用logger插件 

import React from 'react';
import dva from 'dva';
import logger from 'redux-logger'; //引入路由
import router from './router.js';
import counter from './models/counter.js'; // 创建app
const app = dva({ onAction: logger});
//创建路由
app.router(router);
//创建并使用模型
app.model(counter);
//上树运行
app.start("#app");

三、React路由

3.1概述

每个公司用的路由都不一样,这里不一样是很不一样。

dva中依赖react-router-redux,没有改变任何语法

但是react-router-redux出了5代版本,它的2、4、5都特别不一样。

参考文章:

2代:http://www.ruanyifeng.com/blog/2016/05/react_router.html?utm_source=tool.lu

5代:https://blog.csdn.net/isaisai/article/details/78086913

还可以不用官方这个路由,用UI-Router

更可以不用单页面路由,用服务器后端的路由

我们介绍的是单页面的,dva2.3中的react-router-redux 5代的路由写法。


3.2基本使用

react-router-redux是单层级路由,路由的描述,没有任何嵌套

直接路由到最内层组件。

main.js

import React from 'react';
import dva from 'dva';
import logger from 'redux-logger';
// 引入路由
import router from './router.js'; //创建app
const app = dva({ onAction: logger}); //创建路由
app.router(router)
//创建并使用数据模型
app.model(counter)
//上树运行
app.start('#app');

app/router.js路由文件:

import React from 'react';
import { Router, Switch, Route } from 'dva/router'; import Index from "./components/index.js";
import CarList from "./components/carlist/index.js";
import UserList from "./components/userlist/index.js"; export default ({ history, app }) => {
return <Router history={history}>
<Switch>
   <Route exact path="/" component={Index} />
<Route exact path="/carlist" component={CarList} />
<Route exact path="/userlist" component={UserList} />
</Switch>
</Router>
}

这个路由是一个被动的写法:子组件声明我被谁嵌套,而不是父组件声明我嵌套谁。

最内层组件,比如CarList组件,写法:

import React from 'react';
import {connect} from "dva";
import App from "../../containers/App.js";
export default class CarList extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<App menu="汽车">
<div>
<h1>我是Carlist组件</h1>
</div>
</App>
);
}
}

App.js

import React from 'react';
import { connect } from 'dva';
import { Layout, Menu, Breadcrumb, Icon } from 'antd';
import { push } from 'react-router-redux';
const { SubMenu } = Menu;
const { Header, Content, Sider } = Layout; class App extends React.Component {
constructor() {
super()
}
render() {
return <div>
<Layout>
<Header className="header">
<div className="logo" />
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={[this.props.menu]}
style={{ lineHeight: '64px' }}
onClick={(e) => { this.props.dispatch(push(e.item.props.url))}}
> <Menu.Item key="首页" url="/">首页</Menu.Item>
<Menu.Item key="汽车" url="/carlist">汽车</Menu.Item>
<Menu.Item key="用户" url="/userlist">用户</Menu.Item>
</Menu>
</Header>
<Layout>
<Layout style={{ padding: '0 24px 24px' }}>
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item>Home</Breadcrumb.Item>
<Breadcrumb.Item>List</Breadcrumb.Item>
<Breadcrumb.Item>App</Breadcrumb.Item>
</Breadcrumb>
<Content style={{ background: '#fff', padding: 24, margin: 0, minHeight: 280 }}>
{this.props.children}
</Content>
</Layout>
</Layout>
</Layout>,
</div>
}
} export default connect()(App);

路由跳转:

onClick={(e)=>{this.props.dispatch(push(e.item.props.url))}}

前端笔记之React(七)redux-saga&Dva&路由的更多相关文章

  1. react native redux saga增加日志功能

    redux-logger地址:https://github.com/evgenyrodionov/redux-logger 目前Reac native项目中已经使用redux功能,异步中间件使用red ...

  2. 前端笔记之React(三)使用动态样式表&antd&React脚手架&props实战

    一.使用动态样式表 1.1 LESS使用 全局安装Less npm install -g less 创建1.less文件,然后可以用lessc命令来编译这个文件: lessc 1.less 1.css ...

  3. 前端笔记之React(五)Redux深入浅出

    一.Redux整体感知 Redux是JavaScript状态管理容器,提供了可被预测状态的状态管理容器.来自于Flux思想,Facebook基于Flux思想,在2015年推出Redux库. 中文网站: ...

  4. 前端笔记之React(六)ES6的Set和Map&immutable和Ramda和lodash&redux-thunk

    一.ES6的Set.Map数据结构 Map.Set都是ES6新的数据结构,都是新的内置构造函数,也就是说typeof的结果,多了两个: Set 是不能重复的数组 Map 是可以任何东西当做键的对象 E ...

  5. 前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发

    一.React生命周期 一个组件从出生到消亡,在各个阶段React提供给我们调用的接口,就是生命周期. 生命周期这个东西,必须有项目,才知道他们干嘛的. 1.1 Mouting阶段[装载过程] 这个阶 ...

  6. 前端笔记之React(八)上传&图片裁切

    一.上传 formidable天生可以处理上传的文件,非常简单就能持久上传的文件. 今天主要讲解的是,前后端的配合套路. 上传分为同步.异步.同步公司使用非常多,异步我们也会讲解. 1.1 先看一下a ...

  7. 前端笔记:React的form表单全部置空或者某个操作框置空的做法

    1.全部置空的做法,一般在弹出框关闭后,需要重置该form所有表单: this.props.form.resetFields(); 2.针对某个操作框置空的做法 例如,form表单里有一个部门和一个张 ...

  8. 前端笔记之React(二)组件内部State&React实战&表单元素的受控

    一.组件内部的State 1.1 state state叫状态,是每一个类式组件都有的属性,但函数式组件,没有state. state是一个对象,什么值都可以定义. 在任何类式组件的构造函数中,可以用 ...

  9. 前端笔记之React(一)初识React&组件&JSX语法

    一.React项目起步配置 官网:https://reactjs.org/ 文档:https://reactjs.org/docs/hello-world.html 中文:http://react.c ...

随机推荐

  1. OGG For Oracle To PostgreSQL

    本文档描述OGG(Oracle goldengate)为Oracle同步到PostgreSQL数据库配置.在目前去“IOE”潮流.PostgreSQL确实是Oracle最好的替代品之一. 实验环境如下 ...

  2. idea开发时springboot项目时的自动编译和热部署

    前提:最好将idea的启动器设置一下 操作:1.file > Build,Execution,Deployment > Compiler 勾选 Build project automati ...

  3. postgres centos 创建数据库 创建用户

    一个小的流程关于如何创建数据库和用户,用以加强印象,以及留档备份 一.创建账户 1.登录postgres账户 su postgres 2.进入psql 指令 psql 3.创建用户 create US ...

  4. python(二) jupyter 快捷键

    jupyter notebook 中的快捷键介绍 当你熟练使用notebooks的基本功能后,掌握他的快捷键是十分必要的,这样可以大大提高你的工作效率.下面是一些比较常用的快捷键: 编辑模式:点击单元 ...

  5. 09-Node.js学习笔记-异步编程

    同步API,异步API 同步API:只有当前API执行完成后,才能继续执行下一个API console.log('before'); console.log('after'); 异步API:当前API ...

  6. 05-Node.js学习笔记-第三方模块

    5.1什么是第三方模块 别人写好的,具有特定功能的,我们能直接使用的模块即第三方模块,由于第三方模块通常都是由多个文件组成并且被放置在一个文件夹中,所以又名包. 第三方模块有两种存在形式 以js文件的 ...

  7. vue使用--vuex快速学习与使用

    什么是vuex? Vuex核心概念 Vuex安装与使用 1.安装 2.目录结构与vuex引入 3.store中变量的定义.管理.派生(getter) 4.vuex辅助函数的使用说明 Vuex刷新数据丢 ...

  8. java之集合(Set、List、Map)

    java集合类存放于java,uti包中,是一个用于存放对象的容器. 集合只能存放对象,比如存入的是int型数据1,那么它会自动转换成Integer包装类后再存入: 集合存放的是多个对象的引用,对象本 ...

  9. 基于V7的emWin多屏显示方案模板,同时驱动LCD和OLED例程

    说明: 1.多屏驱动跟多图层驱动是类似的,可以使用函数GUI_SelectLayer做切换选择. 2.为了避免OLED闪烁问题,创建一个128*64bit的显存空间,然后使用emWin的GUI_TIM ...

  10. PHP ThinkPHP 非常好用的增删改查方法

    获取列表数据(多条) /*** 获取页面列表* @param $params //查询条件 例:['id'=>['in','1,2']] ['status'=>1]* @param arr ...