前端筆記之React(七)redux-saga&Dva&路由

来源:https://www.cnblogs.com/rope/archive/2019/08/02/10741748.html
-Advertisement-
Play Games

一、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.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文件中發出一個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")
);

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進行處理請求

 刪除actionsconstants文件夾,然後給reducers/counter.jstype都加上引號!

 

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


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非同步

改變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})
        }
    }
}

組件的按鈕沒有變化:

 


 

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代: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))}}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天在項目中,看到了flat的一個語法,是我之前沒有用過的,所以有必要記錄下來,作為新的知識點,鞏固我自己的知識點; 附贈轉載連接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Arr ...
  • 目錄 為什麼要討論this this是什麼 如何改變this的指向 為什麼要討論this this是什麼 如何改變this的指向 為什麼要討論this 代碼一: 會列印出什麼呢?是 ' I am aa ' 嗎, 結果是: 原因: 此時 this 指向 window 對象, this.aa 等同於 w ...
  • 居中是我們使用css來佈局時常遇到的情況。使用css來進行居中時,有時一個屬性就能搞定,有時則需要一定的技巧才能相容到所有瀏覽器,本文就居中的一些常用方法做個簡單的介紹。 註:本文所講方法除了特別說明外,都是相容IE6+、谷歌、火狐等主流瀏覽器的。 先來說幾種簡單的、人畜無害的居中方法: 1. 把m ...
  • 前置閱讀:簡述JavaScript模塊化(一) 在前面一文中,我們對前端模塊化所經歷的三個階段進行了瞭解: CommonJs,由於是同步的,所以主要應用於伺服器端,以Node.js為代表。 AMD,非同步模塊定義,預載入,推薦依賴前置。以require.js為代表。 CMD,通用模塊載入,懶載入,推薦 ...
  • 與數學中的集合概念類似,集合由一組無序的元素組成,且集合中的每個元素都是唯一存在的。可以回顧一下中學數學中集合的概念,我們這裡所要定義的集合也具有空集(即集合的內容為空)、交集、並集、差集、子集的特性。 在ES6中,原生的Set類已經實現了集合的全部特性,稍後我們會介紹它的用法。 我們使用JavaS ...
  • 一、HTML代碼如下: 二、JavaScript代碼如下: ...
  • 輪播效果: HTML: JS: ...
  • 在早期編寫JavaScript時,我們只需在<script>標簽內寫入JavaScript的代碼就可以滿足我們對頁面交互的需要了。但隨著時間的推移,時代的發展,原本的那種簡單粗暴的編寫方式所帶來的諸如邏輯混亂,頁面複雜,可維護性差,全局變數暴露等問題接踵而至,前輩們為瞭解決這些問題提出了很種的解決方 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...