兩週已過,小編攜文來擾!!javascript
最近小編委託組內小哥又給本身梳理了一遍react
結合redux
使用的知識點(由於懶,翻文檔不如白嫖來的開心呀),主要涉及使用的注意事項和使用流程,涉及的中間件以及如何處理異步數據等。完後,小編以爲有必要對此次的知識點作一個系統的整理,造(wu)福(ren)大(zi)衆(di)。html
文中小編對於一些涉及流程的模塊針對性的畫了流程圖,同時穿插了代碼,方便理解(畢竟全是文字會顯得文章很乾)。若觸到只是盲區想細品,小編建議從上往下看;若以爲自身掌握的能夠,可直接跳至文尾,有驚喜!話很少說,安排起來!前端
首先,咱們須要知道的是:
React
是一個聲明式的,高效且靈活的用於構建用戶界面的javascript
庫。React
能夠將一些簡短,獨立的代碼片斷(亦稱‘組件’)組合成複雜的UI
界面。vue
注意: react
是一個庫,而不是框架。java
關於庫: 庫(Lab
)是將代碼集合成的一個產品,以供研發人員調用。庫爲咱們提供了不少封裝好的函數,咱們在使用時只須要提取本身須要的函數便可,使用起來也很是靈活。如果沒有,咱們也能夠手動封裝函數實現。像jQuery
、react
、underscore
就是庫。node
關於框架: 框架(Framework
)則是爲解決一個(一類)問題而開發的產品。通常狀況下,框架用戶只須要使用框架提供的類或函數,便可實現所有功能。像angular
、backbone
、vue
等這些屬於框架。react
舉個例子: 就好比你買了一輛小摩托,小摩托買回來就能夠用了,這裏小摩托就至關於一個框架。而後某天你騎着心愛的小摩托去溜達,發現有人跟你騎着同樣的小摩托,你想讓本身的小摩托變得跟別人不同,就給本身小摩托換個外形或者某個好看的配件。這裏換的配件就至關於庫。web
事實上,庫的使用是很是靈活的,可是沒有框架來的方便,小編認爲這是二者間的主要區別。此外,框架自己是有一套屬於本身的解決方案的,可是react
身爲庫的同時,其自己最大的做用就是用來寫UI
組件,自身並無具有異步處理機制,模塊化以及表單驗證等,主要充當一個前端渲染的庫而已,只有將React
和react-router
,react-redux
,redux-saga
等結合起來使用才稱得上框架。redux
上面已經提到,react
主要純粹是用來寫UI
組件的,它能夠與任何web
程序一塊兒使用。其中,最爲常見的是使用react.js
進行單頁面程序(SPA
)的開發。segmentfault
virtual Dom
,大大提高了渲染性能;virtual Dom
解問決了跨瀏覽器問題,並提供了標準化的API
;上面也講到,react
只是一個純粹寫UI
組件的庫,並非一個框架。在項目開發中,僅僅是使用react
明顯是不夠的(首先數據處理部分就很麻煩),此時須要結合react-router
,react-redux
,redux-saga
等(或者是ReactRouter
和Flux
)使用,才能開發一個項目。
react-router
簡單來說就是經過URL
爲react
頁面導航的路由,它經過管理 URL
,實現組件的切換和狀態的變化。react-router
的核心概念是Router
和Route
。
在這裏,咱們須要明白的一點是,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-router
和react-router-dom
的主要區別在於後者比前者多了<Link>
, <BrowserRouter>
這樣的DOM
類組件,其餘沒啥區別,引入時只需引入一個便可。固然,若是要搭配redux
,還須要引入react-router-redux
。
Router
做爲包裹Route
的容器,當訪問跟路由/
時,組件APP
就會加載到document.getElementById('app')
。當訪問路由/user
時,將會呈現由組件User
渲染構建的UI
頁面。
react-router
最大的做用主要仍是爲react頁面的實現路由跳轉鞠躬盡瘁,保駕護航。
在講react-router
的使用以前,想簡單的講解一下router
的history
。
Router
的history
有三種類型:
HashHistory
和HashRouter
BrowerHistory
和BrowerRouter
reateMemoryHistory
和MemoryRouter
對於以上三種history
,官方上看到推薦使用BrowerHistory
。至於緣由,多是由於使用browserHistory
時,url
格式更加好看吧(小編瞎說的)。不過browserHistory
使用下,瀏覽器表現的url
的格式更加符合通常瀏覽器url
的格式。例子以下:
使用HashHistory
,瀏覽器的url
是這樣的:/#/user/add?page=1
;
使用BrowserHistory
,瀏覽器的url
是這樣的:/user/add
;
相比之下,使用BrowserHistory
,url
表現的形式可能更能被接受。可是須要注意,它須要server
端支持。使用HashHistory
的話,由於帶有#
的緣故,瀏覽器不會去發送request
,react-router
會本身根據路由去渲染相應的模塊(適用於靜態頁面)。
關於react-router
的使用,沒有什麼比官方文檔講解的更全面的吧。
附上連接:reacttraining.com/react-route…
不想看官網,能夠,阮一峯老師講解的也很不錯呀。
連接:www.ruanyifeng.com/blog/2016/0…
不過阮老師寫的這個只適合react-router 2.0
版本的,童鞋們看的時候稍微注意一下。
咱們知道,react
的數據流是自頂向下的單項數據流,數據間的傳遞是經過父組件傳遞給子組件這一方式傳遞的。父組件的state
能夠做爲子組件的props
來傳遞數據,當state
改變時,props
也隨之改變,可是props
自己是不可改變的。
在項目當中,不一樣層級的頁面之間每每須要傳遞數據。當須要傳值的頁面數量變多的狀況下,傳值關係可能會發生混亂。這時候要是有一個容器,可以幫助管理react
的state
的狀態,那就很nice
。接下來就是redux
登場的時刻了!
redux
是javascript
狀態容器,提供可預測化的狀態管理。它能夠構建一致化的應用,運用於不一樣的環境(客戶端、服務器端、原生應用),而且易於測試。
1.Action
action
是惟一能夠改變狀態的途徑,同時它也是store
的惟一數據來源。通常狀況下,經過dispatch
觸發相應的action
,從而達到改變store
中state
的目的。(注意,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
是把action
和reducer
聯繫起來的一個對象。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()
複製代碼
小編給他們之間的關係畫了個圖,以下:
當項目較爲簡單,沒有過多的交互,View
只從單一來源獲取數據或不須要與服務器大量交互(或不使用websocket
)時,能夠不使用redux
(使用了可能必定程度上會使項目變得更復雜)。
可是如下幾種狀況可使用redux
:
WebSocket
View
要從多個來源獲取數據從組件的角度看,如下幾種狀況可使用redux
:
拓展:若是對爲何react
要使用redux
還有不瞭解的童鞋,能夠去看看下面連接的內容,小編以爲看完確定就明瞭了。
連接: segmentfault.com/a/119000001…
react-redux
是redux
的官方react
的綁定庫。它可以使你的react
組件在redux
的store
中讀取數據,並向store
分發actions
以便更新數據。
react-redux
將全部的組件分爲兩大類:分別是UI
組件(presentational component
,又稱傻瓜組件/無狀態組件)和容器組件(container component
)。
UI
組件具備如下幾個特徵:
UI
呈現,不帶有任何的業務邏輯UI
的渲染只能經過外部傳入props
來改變(也就是不使用this.state
)this.props
)對象提供redux
的API
簡單的來講,UI
組件就是負責頁面的渲染。
舉個例子:
const page = number => <p>this is {number} page </p>
複製代碼
容器組件和UI
組件在必定程度上偏偏相反:
redux
的API
簡單來講,容器組件就是負責管理數據以及處理頁面的業務邏輯。
注意: 如果一個組件內既有UI
組件又有邏輯,能夠考慮將其拆分紅外面是一個容器組件,裏面包含一個UI組件的結構。前者負責與外部通訊,將數據傳給後者,後者負責頁面的渲染。
React-Redux
規定,全部的 UI
組件都由用戶提供,容器組件則是由 React-Redux
自動生成。也就是說,用戶負責視覺層,狀態管理則是所有交給它。
react-redux
提供了兩個重要的API
:connect
和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
的做用就是將UI
組件和容器組件連接起來,本質的做用其實就是充當一個鏈接器。
connect()
接受四個參數,可是通常狀況下最常使用的是前兩種:
connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {})(component)
複製代碼
1.mapStateToProps
做爲connect
的第一個參數,mapStateToProps
用來從store
中選擇被鏈接的組件所須要的數據。
注意:
store
的state
改變時,就會被調用store
的state
,而且返回組件所須要的數據舉個例子:
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)
。
mergeProps
是connect
的第三個參數,可選。它將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
, 咱們須要重點掌握它提供的兩個組件--provider
和connect
,它們的用法。同時對於其它相關概念也須要了解一下。
redux-saga
是一個用於管理應用程序Side Effect
(反作用,例如異步獲取數據,訪問瀏覽器緩存等)的Library
,它的目標是讓反作用管理更容易,執行更高效,測試更簡單,在處理故障時更容易。
能夠簡單的理解爲,redux-saga
在redux
中扮演着‘中間件’的角色,主要做用是用來執行redux
中數據的異步操做。在執行異步操做時,須要藉助ES6
中的generator
函數和yield
關鍵字來以同步的方式實現異步操做。(它的功能有點像redux-thunk+async/await
,經過建立 Sagas
將全部的異步操做邏輯都存放在一個地方進行集中處理)
由於redux
中的action
須要redux-thunk
或者redux-saga
這樣的‘中間件’去作異步處理。(就一句話,簡潔明瞭吧)
流程圖以下:
簡單來講就是:ui
組件觸發action
建立函數 -> action
建立函數返回一個action
-> action
被傳入redux
中間件(被 saga
等中間件處理) ,產生新的action
,傳入reducer
-> reducer
把數據傳給ui
組件顯示 ->mapStateToProps
-> ui
組件顯示
call
異步阻塞調用put
至關於dispatch
,分發一個action
select
至關於getState
,用於從store
中獲取響應的state
fork
異步非阻塞調用,無阻塞的執行fn
,執行fn
時,不會暫停Generator
tak
e監聽action
,暫停Generator
,匹配的action
被髮起時,恢復執行。take
結合fork
,能夠實現takeEvery
和takeLatest
的效果takeEvery
監聽監聽action
,每監聽到一個action
,就執行一次操做takeLatest
監聽action
,監聽到多個action
,只執行最近的一次cancel
指示middleware
取消以前的fork
任務,cancel
是一個無阻塞的Effect
。也就是說,Generator
將在取消異常被拋出後當即恢復race
競速執行多個任務throttle
節流優勢
action
是個普通對象,與redux
的action
保持一致Effect
),方便異步接口的測試worker
和watcher
能夠實現非阻塞異步調用,同時能夠實現非阻塞調用下的事件監聽缺點:相對於新手來講,學習難度有點大,成本有點高(如果不考慮學習成本,建議用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
的一箇中間件(middleware
)。當dispatch
一個action
以後,到達reducer
以前,進行一些額外的操做時(處理action
反作用),就須要使用到redux-thunk
。它的做用跟redux-saga
相似,都是用來處理異步數據。
有關redux-thunk
,跟saga
相比,其實小編以爲並無說哪一個更好,或者哪一個很差,主要是看我的更加擅長使用哪一個吧。在這裏小編就很少說了,有關這部分的知識點,小編建議能夠去看看阮一峯老師寫的文檔。
爲了省去文字,更加清晰的將文中所述的知識點鏈接起來,小編嘗試畫了個圖,供理解。以下:
(如有疏漏,請留言指,手動筆芯~)
關於react
結合redux
以及react-router(react-router-dom)
,redux-saga
等,小編就總結到這兒吧。劃重點:主要了解他們之間的聯繫並懂的使用。
嘮嗑一下,最近小編髮現,有的童鞋關注了,收藏了,可是就輕飄飄的溜了,這是咋回事兒?!
仍是內句老話,如果發現小編哪兒梳理的有問題,歡迎下方留言,小編瞅到必定及時更正。若覺尚可,嘿嘿(整理不易,小編也須要鼓勵)。