關於react結合redux使用,或許你還應該掌握這些(圖文結合,悉心整理)

兩週已過,小編攜文來擾!!javascript

最近小編委託組內小哥又給本身梳理了一遍react結合redux使用的知識點(由於懶,翻文檔不如白嫖來的開心呀),主要涉及使用的注意事項和使用流程,涉及的中間件以及如何處理異步數據等。完後,小編以爲有必要對此次的知識點作一個系統的整理,造(wu)福(ren)大(zi)衆(di)。html

文中小編對於一些涉及流程的模塊針對性的畫了流程圖,同時穿插了代碼,方便理解(畢竟全是文字會顯得文章很乾)。若觸到只是盲區想細品,小編建議從上往下看;若以爲自身掌握的能夠,可直接跳至文尾,有驚喜!話很少說,安排起來!前端


react

react概念

首先,咱們須要知道的是:React是一個聲明式的,高效且靈活的用於構建用戶界面的javascript庫。React能夠將一些簡短,獨立的代碼片斷(亦稱‘組件’)組合成複雜的UI界面。vue

注意: react是一個庫,而不是框架。java

拓展: 庫和框架有什麼區別?

關於庫: 庫(Lab)是將代碼集合成的一個產品,以供研發人員調用。庫爲咱們提供了不少封裝好的函數,咱們在使用時只須要提取本身須要的函數便可,使用起來也很是靈活。如果沒有,咱們也能夠手動封裝函數實現。像jQueryreactunderscore就是庫。node

關於框架: 框架(Framework)則是爲解決一個(一類)問題而開發的產品。通常狀況下,框架用戶只須要使用框架提供的類或函數,便可實現所有功能。像angularbackbonevue等這些屬於框架。react

舉個例子: 就好比你買了一輛小摩托,小摩托買回來就能夠用了,這裏小摩托就至關於一個框架。而後某天你騎着心愛的小摩托去溜達,發現有人跟你騎着同樣的小摩托,你想讓本身的小摩托變得跟別人不同,就給本身小摩托換個外形或者某個好看的配件。這裏換的配件就至關於庫。web

小結

事實上,庫的使用是很是靈活的,可是沒有框架來的方便,小編認爲這是二者間的主要區別。此外,框架自己是有一套屬於本身的解決方案的,可是react身爲庫的同時,其自己最大的做用就是用來寫UI組件,自身並無具有異步處理機制,模塊化以及表單驗證等,主要充當一個前端渲染的庫而已,只有將Reactreact-router,react-redux,redux-saga等結合起來使用才稱得上框架。redux

react做用

上面已經提到,react主要純粹是用來寫UI組件的,它能夠與任何web程序一塊兒使用。其中,最爲常見的是使用react.js進行單頁面程序(SPA)的開發。segmentfault

react優勢以及存在的不足

react的優勢

  • 使用了virtual Dom,大大提高了渲染性能;
  • 代碼組件化,便於複用,使用起來更加方便也更容易維護;
  • virtual Dom解問決了跨瀏覽器問題,並提供了標準化的API
  • 可以很好的和現有代碼結合使用;
  • 易理解,易上手;

存在的不足

上面也講到,react只是一個純粹寫UI組件的庫,並非一個框架。在項目開發中,僅僅是使用react明顯是不夠的(首先數據處理部分就很麻煩),此時須要結合react-router,react-redux,redux-saga等(或者是ReactRouterFlux)使用,才能開發一個項目。


react-router

react-router概念

react-router簡單來說就是經過URLreact頁面導航的路由,它經過管理 URL,實現組件的切換和狀態的變化。react-router的核心概念是RouterRoute

在這裏,咱們須要明白的一點是,Router在這裏做爲一個容器,用於包裹Route。具體的路由跳轉是由Route實現的。

舉個栗子:

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";

ReactDOM.render(
  <Router>
    <Route path="/" component={App} />
    <Route path="/user" component={User} />
    ...
  </Router>,
  node
);
複製代碼

說明:在這裏引入了react-router-dom。其實react-routerreact-router-dom的主要區別在於後者比前者多了<Link><BrowserRouter>這樣的DOM類組件,其餘沒啥區別,引入時只需引入一個便可。固然,若是要搭配redux,還須要引入react-router-redux

Router做爲包裹Route的容器,當訪問跟路由/時,組件APP就會加載到document.getElementById('app')。當訪問路由/user時,將會呈現由組件User渲染構建的UI頁面。

react-router做用

react-router最大的做用主要仍是爲react頁面的實現路由跳轉鞠躬盡瘁,保駕護航。

react-router如何使用

在講react-router的使用以前,想簡單的講解一下routerhistory

Routerhistory有三種類型:

  • HashHistoryHashRouter
  • BrowerHistoryBrowerRouter
  • reateMemoryHistoryMemoryRouter

對於以上三種history,官方上看到推薦使用BrowerHistory。至於緣由,多是由於使用browserHistory時,url格式更加好看吧(小編瞎說的)。不過browserHistory使用下,瀏覽器表現的url的格式更加符合通常瀏覽器url的格式。例子以下:

  • 使用HashHistory,瀏覽器的url是這樣的:/#/user/add?page=1

  • 使用BrowserHistory,瀏覽器的url是這樣的:/user/add

相比之下,使用BrowserHistoryurl表現的形式可能更能被接受。可是須要注意,它須要server端支持。使用HashHistory的話,由於帶有#的緣故,瀏覽器不會去發送requestreact-router會本身根據路由去渲染相應的模塊(適用於靜態頁面)。

關於react-router的使用,沒有什麼比官方文檔講解的更全面的吧。

附上連接:reacttraining.com/react-route…

不想看官網,能夠,阮一峯老師講解的也很不錯呀。

連接:www.ruanyifeng.com/blog/2016/0…

不過阮老師寫的這個只適合react-router 2.0版本的,童鞋們看的時候稍微注意一下。


redux

redux概念

咱們知道,react的數據流是自頂向下的單項數據流,數據間的傳遞是經過父組件傳遞給子組件這一方式傳遞的。父組件的state能夠做爲子組件的props來傳遞數據,當state改變時,props也隨之改變,可是props自己是不可改變的。

在項目當中,不一樣層級的頁面之間每每須要傳遞數據。當須要傳值的頁面數量變多的狀況下,傳值關係可能會發生混亂。這時候要是有一個容器,可以幫助管理reactstate的狀態,那就很nice。接下來就是redux登場的時刻了!

什麼是redux以及其做用?

reduxjavascript狀態容器,提供可預測化的狀態管理。它能夠構建一致化的應用,運用於不一樣的環境(客戶端、服務器端、原生應用),而且易於測試。

redux的幾個核心概念

1.Action

action是惟一能夠改變狀態的途徑,同時它也是store的惟一數據來源。通常狀況下,經過dispatch觸發相應的action,從而達到改變storestate的目的。(注意,action是一個對象)

舉個例子:

const action = {
      type'xxx/add',     // xxx是namespace名,type屬性爲必須
      payload: {name: 'phoebe'},
      ...     //可根據需求寫callback()回調函數
  }
複製代碼

2.Reducer

reducer,簡單的說就是一個函數,它接受由dispatch觸發的action和當前的state做爲一個參數,返回一個新的state(簡單的說就是根據action來更新state)。

舉個例子:

const Reducer = ({state, action}) => {
    ...
    return newState; //返回的新的state
}
複製代碼

3.Store

Store是把actionreducer聯繫起來的一個對象Store能夠理解爲一個存儲數據的倉庫,管理着整個應用的狀態。

注意: Redux 應用只有一個單一的 store

Store的職責:

  • 維持應用的 state
  • 提供getState()方法獲取 state
  • 提供 dispatch(action) 方法更新 state
  • 經過 subscribe(listener) 註冊監聽器;
  • 經過 subscribe(listener) 返回的函數註銷監聽器。

Redux經過 createStore 這個函數,來生成store對象:

import { createStore } from 'redux';
import todoApp from './reducers';

let store = createStore(todoApp)
複製代碼

同時,想獲取到當前的state時,能夠經過getState()這個方法來獲取:

const state = store.getState()
複製代碼

小編給他們之間的關係畫了個圖,以下:

什麼狀況下須要redux?

當項目較爲簡單,沒有過多的交互,View只從單一來源獲取數據或不須要與服務器大量交互(或不使用websocket)時,能夠不使用redux(使用了可能必定程度上會使項目變得更復雜)。

可是如下幾種狀況可使用redux:

  • 用戶的使用方式複雜
  • 不一樣身份的用戶有不一樣的使用方式(好比普通用戶和管理員)
  • 多個用戶之間能夠協做
  • 與服務器大量交互,或者使用了WebSocket
  • View要從多個來源獲取數據

從組件的角度看,如下幾種狀況可使用redux:

  • 某個組件的狀態,須要共享
  • 某個狀態須要在任何地方均可以拿到
  • 一個組件須要改變全局狀態
  • 一個組件須要改變另外一個組件的狀態

拓展:若是對爲何react要使用redux還有不瞭解的童鞋,能夠去看看下面連接的內容,小編以爲看完確定就明瞭了。

連接: segmentfault.com/a/119000001…


react-redux

react-redux概念及做用

react-reduxredux的官方react的綁定庫。它可以使你的react組件在reduxstore中讀取數據,並向store分發actions以便更新數據。

react-redux的分類

react-redux將全部的組件分爲兩大類:分別是UI組件(presentational component,又稱傻瓜組件/無狀態組件)和容器組件(container component)。

UI組件

UI組件具備如下幾個特徵:

  • 只負責UI呈現,不帶有任何的業務邏輯
  • 沒有狀態,UI的渲染只能經過外部傳入props來改變(也就是不使用this.state
  • 全部的數據都由參數(this.props)對象提供
  • 不使用任何reduxAPI

簡單的來講,UI組件就是負責頁面的渲染。

舉個例子:

const page = number => <p>this is {number} page </p>
複製代碼

容器組件

容器組件和UI組件在必定程度上偏偏相反:

  • 負責管理數據和業務邏輯,不負責頁面渲染
  • 帶有內部狀態
  • 使用reduxAPI

簡單來講,容器組件就是負責管理數據以及處理頁面的業務邏輯。

注意: 如果一個組件內既有UI組件又有邏輯,能夠考慮將其拆分紅外面是一個容器組件,裏面包含一個UI組件的結構。前者負責與外部通訊,將數據傳給後者,後者負責頁面的渲染。

React-Redux 規定,全部的 UI 組件都由用戶提供,容器組件則是由 React-Redux 自動生成。也就是說,用戶負責視覺層,狀態管理則是所有交給它。

react-redux兩個重要的API

react-redux提供了兩個重要的APIconnectProvider

Provider組件

react-redux提供了<Provider>組件,用於鏈接Store,把store提供給內部組件,內部組件接受store做爲props,而後經過context往下傳,這樣react中任何組件均可以經過context獲取store(簡單來講就是使得咱們的整個app都能訪問到redux store中的數據)。

舉個例子:

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import store from './store';
import App from './App';

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />   // Provider的子組件能夠拿到store狀態
  </Provider>,
  rootElement
);
複製代碼

同時,React-Redux提供一個connect方法,讓咱們能夠把組件和store鏈接起來。

import { connect } from "react-redux";
import { increment, decrement, reset } from "./actionCreators";

// const Counter = ...

const mapStateToProps = (state /*, ownProps*/) => {
  return {
    counter: state.counter
  };
};

const mapDispatchToProps = { increment, decrement, reset };

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);
複製代碼

這樣,咱們就能從store中獲取相應的數據到Counter中。

connect()

connect的做用就是將UI組件和容器組件連接起來,本質的做用其實就是充當一個鏈接器。

connect()接受四個參數,可是通常狀況下最常使用的是前兩種:

connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {})(component)
複製代碼

1.mapStateToProps

做爲connect的第一個參數,mapStateToProps用來從store中選擇被鏈接的組件所須要的數據。

注意:

  • 每當storestate改變時,就會被調用
  • 接收整個storestate,而且返回組件所須要的數據

舉個例子:

import React from 'react';
import { connect } from 'dva';

const Alarm = ({permission, ....}) => {     // 定義的函數組件
    //...
};

const mapStateToProps = ({ app }) => {    //從store中摘出app
    const { permission } = app;     //從app中摘出組件須要的權限
    return {
        permission, 
    };
};

export default connect(mapStateToProps)(Alarm);
複製代碼

2.mapDispatchToProps

做爲第二個傳入connect的參數,mapDispatchToProps能夠實現向store中分發acions。這也是惟一觸發一個state變化的途徑。它是用來創建UI 組件的參數到store.dispatch方法的映射,能夠是一個函數,也能夠是一個對象。

React-Redux提供了兩種能夠分發actions的方式:

  • 默認地,一個已鏈接組件能夠接收props.dispatch而後本身分發actions
  • connect可以接收一個mapDispatchToProps做爲第二個參數,這可讓咱們可以建立dispatch調用方法,而後把這些方法做爲props傳遞給咱們的組件。

當咱們不把mapDispatchToProps做爲connect的第二個參數傳入時,看下官方例子:

connect()(MyComponent);
// 與下面語句等價
connect(
  null,
  null
)(MyComponent);

// 或者
connect(mapStateToProps /** 沒有第二個參數 */)(MyComponent);
複製代碼

如果咱們使用這種方式,咱們的組件就會接收props.dispatch,它能夠用來分發組件中的actions

看個更詳細的例子:

import React from 'react';
import { connect } from 'dva';

const Alarm = ({dispatch, permission, ....}) => {     // Alarm組件接收props的dispatch
    const onAdd = () => {
        dispatch({        //dispatch 用於觸發onAdd方法的action
            type: 'xxx',
            payload: {...}
        })
    }
    return(
      <div onClick={onAdd}>   //添加觸發事件
         //...
      <div>
    )
};

const mapStateToProps = ({ app }) => {    
    const { permission } = app;     
    return {
        permission, 
    };
};

export default connect(mapStateToProps)(Alarm);
複製代碼

小結:有關mapDispatchToProps部分,小編主要結合所作項目作了相應的總結,關於它的函數形式和對象形式,有興趣的童鞋能夠點擊此處瞭解詳情。

3.mergeProps

mergeProps的格式爲: mergeProps(stateProps, dispatchProps, ownProps)

mergePropsconnect的第三個參數,可選。它將mapStateToProps()mapDispatchToProps()返回的對象結果和組件自身的props合併成新的props,而後傳入組件。默認返回Object.assign({}, ownProps, stateProps, dispatchProps)的結果。

寫成例子以下:

const mergeProps = () => {
      return Object.assign({}, ownProps, stateProps, dispatchProps)
}
複製代碼

4.options

做爲connect的第四個參數,經過配置項能夠更加詳細的定義connect的行爲,通常狀況下只須要執行默認值。option有不少,舉個官方例子:

{
  context?: Object,
  pure?: boolean,
  areStatesEqual?: Function,
  areOwnPropsEqual?: Function,
  areStatePropsEqual?: Function,
  areMergedPropsEqual?: Function,
  forwardRef?: boolean,
}
複製代碼

小結:關於react-redux, 咱們須要重點掌握它提供的兩個組件--providerconnect,它們的用法。同時對於其它相關概念也須要了解一下。


redux-saga

redux-saga概念及其做用

redux-saga是一個用於管理應用程序Side Effect(反作用,例如異步獲取數據,訪問瀏覽器緩存等)的Library,它的目標是讓反作用管理更容易,執行更高效,測試更簡單,在處理故障時更容易。

能夠簡單的理解爲,redux-sagaredux中扮演着‘中間件’的角色,主要做用是用來執行redux中數據的異步操做。在執行異步操做時,須要藉助ES6中的generator函數和yield關鍵字來以同步的方式實現異步操做。(它的功能有點像redux-thunk+async/await,經過建立 Sagas 將全部的異步操做邏輯都存放在一個地方進行集中處理)

爲何要使用redux-saga

由於redux中的action須要redux-thunk或者redux-saga這樣的‘中間件’去作異步處理。(就一句話,簡潔明瞭吧)

redux-saga的執行流程

流程圖以下:

簡單來講就是:ui組件觸發action建立函數 -> action建立函數返回一個action -> action被傳入redux中間件(被 saga等中間件處理) ,產生新的action,傳入reducer -> reducer把數據傳給ui組件顯示 ->mapStateToProps -> ui組件顯示

effect提供的常見的建立器及其用法有哪些

  • call異步阻塞調用
  • put至關於dispatch,分發一個action
  • select至關於getState,用於從store中獲取響應的state
  • fork異步非阻塞調用,無阻塞的執行fn,執行fn時,不會暫停Generator
  • take監聽action,暫停Generator,匹配的action被髮起時,恢復執行。take結合fork,能夠實現takeEverytakeLatest的效果
  • takeEvery監聽監聽action,每監聽到一個action,就執行一次操做
  • takeLatest監聽action,監聽到多個action,只執行最近的一次
  • cancel指示middleware取消以前的fork任務,cancel是一個無阻塞的Effect。也就是說,Generator將在取消異常被拋出後當即恢復
  • race競速執行多個任務
  • throttle節流

redux-saga的優缺點

優勢

  • 能夠集中處理異步操做,使得異步接口一目瞭然
  • action是個普通對象,與reduxaction保持一致
  • 經過反作用(Effect),方便異步接口的測試
  • 經過workerwatcher能夠實現非阻塞異步調用,同時能夠實現非阻塞調用下的事件監聽

缺點:相對於新手來講,學習難度有點大,成本有點高(如果不考慮學習成本,建議用redux-saga

結合示例

幹講可能有些童鞋會迷惑,下面小編舉個代碼例子講一下redux-saga在代碼中具體是怎樣異步處理數據的。

注意,如下是小編從demo中抽取的一個頁面代碼,爲了方便理解,全部組件都包含在<APP></APP>中,APP做爲父組件,它 的state將做爲全部子組件的props(能理解吧?)。

// magagement.js

import React from 'react';
import { connect } from 'dva';
import { Card, Select } from 'antd';
import Page from 'components/Page';
import Search from 'components/Search';

const Management = ({dispatch, management, permission }) = {

    addClick = () => {   
        dispatch({    //當點擊按鈕時,觸發action,就是文中所說的‘點擊UI組件觸發action’
             type: 'management/add',      // 觸發以後將action中的type和當前payload傳到reducer
             payload: { name: phoebe },
         });
    };
    
    return (
        <Page title="xxx">
          <Card>
            <Search
              extra={
                permission.includes('xxx/xxxx') && (
                  <Button type="primary" icon="plus" onClick={addClick}>  //爲按鈕添加一個觸發事件
                    一個小可愛呀
                  </Button>
                )
              }
            />
          </Card>
        </Page>    
    )
};

const mapStateToProps = ({  app, management }) => {   // 從store中抽出Managenent須要的數據
  const { permission } = app;
  return {
    management,
    permission,
  };
};

export default connect(mapStateToProps)(Management); // 利用connect,將抽出的數據做爲子組件的props傳入

// Management.js的Models

import modelExtend from 'dva-model-extend';
import { pageModel } from 'models/common';
import { addManagement } from 'services/firmware';  // 接口

export default modelExtend(pageModel, {
    namespace: 'management',
    
    state: {},
    
    subscriptions: {
        setup({ dispatch, history }) {
            history.listen(location => {
                if (location.pathname === 'xxx/managenent') {
                   //...
                }
            })
        }
    },
    
    effects: {    //redux-saga管理反作用(effect),具體做用體如今這兒
        //...
        *add({ payload }, { call, put }) {
            const data = yield call(xxx, payload);  // 將處理的數據上傳接口,以後UI更新顯示
            yield put({       // 建立並 yield 一個 dispatch Effect
                type: 'updateState',
                payload: {
                    name: 'Tins',
                },
            });
            //...
        },
    },
});

//Management的路由
import React from 'react';
import { routerRedux, Route, Switch } from 'dva/router';
import App from 'routes/app';
import { LocaleProvider, Spin } from 'antd';
import zhCN from 'antd/lib/locale-provider/zh_CN';
import moment from 'moment';
import 'moment/locale/zh-cn';

moment.locale('zh-cn');
const { ConnectedRouter } = routerRedux;

function RouterConfig({ history, app }) {
    //....
    const Management = dynamic({
        app,
        component: () => import('./xxx'),
    });
    return (
        <ConnectedRouter history={history}>
          <LocaleProvider locale={zhCN}>
            <App>
                <Switch>
                    //...
                    <Route path="/xxx" exact component={Management} />  // Mamagement做爲APP的子組件,APP的store state將做爲Management的props。
                </Switch>
            </App>
          </LocaleProvider>
        </ConnectedRouter>
  );
}
複製代碼

小結: 關於redux-saga,使用的大體流程就是這樣,以爲有不明白或者小編有描述不清晰的請留言。更加詳細的知識點能夠去redux-saga官網瞅瞅。


redux-thunk

redux-thunk概念及其做用

redux-thunk也是redux的一箇中間件(middleware)。當dispatch一個action以後,到達reducer以前,進行一些額外的操做時(處理action反作用),就須要使用到redux-thunk。它的做用跟redux-saga相似,都是用來處理異步數據。

有關redux-thunk,跟saga相比,其實小編以爲並無說哪一個更好,或者哪一個很差,主要是看我的更加擅長使用哪一個吧。在這裏小編就很少說了,有關這部分的知識點,小編建議能夠去看看阮一峯老師寫的文檔


綜合圖解

爲了省去文字,更加清晰的將文中所述的知識點鏈接起來,小編嘗試畫了個圖,供理解。以下:

(如有疏漏,請留言指,手動筆芯~)


總結

關於react結合redux以及react-router(react-router-dom),redux-saga等,小編就總結到這兒吧。劃重點:主要了解他們之間的聯繫並懂的使用

嘮嗑一下,最近小編髮現,有的童鞋關注了,收藏了,可是就輕飄飄的溜了,這是咋回事兒?!

仍是內句老話,如果發現小編哪兒梳理的有問題,歡迎下方留言,小編瞅到必定及時更正。若覺尚可,嘿嘿(整理不易,小編也須要鼓勵)。

相關文章
相關標籤/搜索