欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

前端笔记之React(七)redux-saga&Dva&路由

程序员文章站 2022-08-17 18:01:11
一、redux-saga解决异步 redux-thunk 和 redux-saga 使用redux它们是必选的,二选一,它们两个都可以很好的实现一些复杂情况下redux,本质都是为了解决异步action而生,使redux保持完整性,不至于太过混乱。redux-saga 是一个用于管理Redux 应用 ......

一、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-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文件中发出一个actionaddserver,但异步还没发出:

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.jssaga开始工作,它要开始劫持监听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")
);

前端笔记之React(七)redux-saga&Dva&路由

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;
}

前端笔记之React(七)redux-saga&Dva&路由

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

 删除actionsconstants文件夹,然后给reducers/counter.jstype都加上引号!

前端笔记之React(七)redux-saga&Dva&路由

 

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这个库,“集大成”者,本质的目的就是让程序“浑然一体”,一方面是方便起步,更大的发明利于团队组件开发,不用频繁在组件文件actionssagareducers文件之间进行切换了。


2.2 hello world

npm install --save dva

dva中集成了reduxreact-reduxredux-sagareact-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>
}

前端笔记之React(七)redux-saga&Dva&路由


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>
}

前端笔记之React(七)redux-saga&Dva&路由


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中引包。

 前端笔记之React(七)redux-saga&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
            }
        }
    }
}

前端笔记之React(七)redux-saga&Dva&路由


2.6异步

改变modelcounter.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})
        }
    }
}

组件的按钮没有变化:

 前端笔记之React(七)redux-saga&Dva&路由


 

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代版本,它的245都特别不一样。

参考文章:

2代:

5代:

 

还可以不用官方这个路由,用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))}}