寫到系列三的時候,感覺有點(diǎn)乏力了。一個人研究,結(jié)合眾多大神走過的路,還是難免不斷地掉坑里,好希望抱個大腿,獨(dú)行太浪費(fèi)時間了。不過幸好,寫到系列三,難點(diǎn)基本已經(jīng)結(jié)束,剩下難度高點(diǎn)的無非是表結(jié)構(gòu),以及工作平臺如何設(shè)計才能公共化。
在開始介紹react-redux之前,很有必要簡單論述下redux。redux是一個狀態(tài)管理器,它負(fù)責(zé)狀態(tài)的增刪查改,類似mysql一樣,可以說,redux跟react框架是沒有什么關(guān)系的,react-redux是為了更方便使用redux管理項(xiàng)目而創(chuàng)造的一個庫。
redux的基本流程:在view中觸發(fā)dispatch,dispatch將action發(fā)送到reducer中后,reducer根據(jù)action更新相應(yīng)的state,進(jìn)而改變頁面view。從基本流程上可以知道,redux重點(diǎn)關(guān)注的是--Actions,Reducers,Store,State。
- Action-- 一個行為對象,用于dispatch,告訴reducer需要執(zhí)行的哪個操作。
- Reducers-- 可以將其看成事件處理中心,由多個事件處理函數(shù)組成,當(dāng)觸發(fā)某個行為時,從而引發(fā)reducer中的對應(yīng)處理函數(shù),這里也是更新state的地方。
- Store-- 管理state的單一對象,你可以將其看成一個倉庫,用來儲藏state。核心方法:store.getState()-獲取state,store.dispatch(action)-更新state
- State -- 指的是全局狀態(tài)樹,可以理解為數(shù)據(jù)庫,臨時儲存項(xiàng)目的數(shù)據(jù)。
react-redux庫將組件統(tǒng)一分成兩大類:UI組件和容器組件,UI組件是指單純的渲染頁面組件,數(shù)據(jù)通過this.props獲取,不設(shè)置state;而容器組件則負(fù)責(zé)管理數(shù)據(jù)和業(yè)務(wù)邏輯,redux的API就是在容器組件中調(diào)用。因此,實(shí)際上,最外層一般為容器組件,負(fù)責(zé)數(shù)據(jù)的獲取、更新,里面包裹對應(yīng)的視覺層組件,渲染頁面。
那么,redux是如何與容器組件關(guān)聯(lián)起來的呢?react-redux庫提供了一個組件和一個方法。
- connect()方法
// Index為容器組件
export default connect(
mapStateToProps,
mapDispatchToProps
)(Index);
可以看到,connect()方法傳入了兩個參數(shù)mapStateToProps和mapDispatchToProps。這是connect四個屬性中比較重要的兩個,開發(fā)中一般也只用到這兩個。
- mapStateToProps:其必定返回一個對象,其作用是將狀態(tài)綁定到組件的props屬性中
function mapStateToProps(state) {
const { websites } = state;
return {
data: websites
}
}
- mapDispatchToProps:返回一個對象。其方法是對store.dispatch(action)的封裝。也是在該處進(jìn)行事件的定義,每個定義在該對象的函數(shù)都將被當(dāng)作 Redux action creator,而且這個對象會與 Redux store 綁定在一起,方法名將作為屬性名合并到組件的props屬性中,這樣組件就可以直接調(diào)用該方法。
function mapDispatchToProps(dispatch, ownProps) {
return {
onIncreaseClick: (msg) => dispatch({type: "websites_increase", data: msg}),
onReduceClick: (msg) => dispatch({type: "websites_reduce", data: msg})
}
}
- 同時,在mapDispatchToProps中定義的事件方法,具體的處理函數(shù)均在相應(yīng)的reducer中。
// 如在某個reducer中:
function websiteReducer(state = {count: 0}, action) {
let newState = state;
let count = null;
switch (action.type) {
case 'websites_increase':
count = state.count;
newState ["count"] = count + 1;
return Object.assign({}, state, newState );
case 'websites_reduce':
count = state.count;
newState = action.data;
return Object.assign({}, state, newState );
default:
return Object.assign({}, state);
}
}
export default websiteReducer;
一個reducer就會返回state樹中的一部分(如果只有一個reducer,則這個reducer返回的是整個state樹,因此,state中的數(shù)據(jù)結(jié)構(gòu)就是各個子reducer的集合),而在mapStateToProps方法中傳入的入?yún)tate是整個狀態(tài)樹的。那么,有多個reducer,該如何合并成一個總的reducer呢,又是如何生成整個state狀態(tài)樹呢。Redux提供了兩個方法combineReducers和createStore,
// 使用combineReducers進(jìn)行合并reducer
import { createStore, combineReducers } from 'redux';
import websiteReducer from './websiteReducer';
import siteReducer from './siteReducer';
const appReducer = combineReducers({
websites : websiteReducer,
sites : siteReducer
})
// 使用createStore接收reducer函數(shù)和初始化的數(shù)據(jù)(currentState),生成store
const store = createStore(appReducer);
值得注意的是:store根據(jù)dispatch之后返回的action對象中的 type 屬性來執(zhí)行相關(guān)任務(wù),也就是說只要帶有相同 type 屬性值的reducer都會執(zhí)行。因此,建議不同的reducer中的type使用當(dāng)前key作為前綴。
store有了,這意味著state樹已經(jīng)生成并保存在store中,那么該如何將其傳遞到每一個組件中呢?這是就要用到了react-redux庫中的Provider組件。
通過在最外層再包裹一層Provider組件即可
<Provider store={ store }>
<BrowserRouter history={browserHistory} basename="/app">
<div className="body-content">
<div>測試</div>
<Form />
<Switch>
<Route exact path="/" render={() => pathCom()} />
<Route exact path="/index" component={Index} />
<Route path="/userLogin" component={UserLogin} />
</Switch>
</div>
</BrowserRouter>
</Provider>
這樣一來,組件內(nèi)就可以直接引用屬性和相應(yīng)的方法
class Index extends React.Component{
handleReduceClick() {
let data = this.props.data;
data.count = data.count - 1;
this.props.onReduceClick(data);
}
render() {
const { data, onIncreaseClick, onReduceClick } = this.props;
return (
<div className="productBox">
數(shù)值:{data.count}
<button onClick={onIncreaseClick} >加法</button>
<button onClick={this.handleReduceClick.bind(this)} >減法</button>
</div>
);
}
};
然而,Redux本身只能處理同步的action,但大多數(shù)前端業(yè)務(wù)邏輯都必須和后端產(chǎn)生一次或多次異步交互,這就需要一個中間件來實(shí)現(xiàn)異步了。在這里我使用官方推薦的redux-thunk。
在這里需要稍微修改下reducer.js文件
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
//異步操作
import promiseMiddleware from 'redux-promise';
//記錄日志
import { logger, createLogger } from 'redux-logger';
import websiteReducer from './websiteReducer';
import siteReducer from './siteReducer';
import ajaxReducer from './ajaxReducer';
// RootReducer
const appReducer = combineReducers({
websites : websiteReducer,
sites : siteReducer,
ajax : ajaxReducer
// websiteReducer,
// siteReducer
})
const loggerMiddleware = createLogger()
const store = createStore(
appReducer,
applyMiddleware(
thunkMiddleware, // 允許我們 dispatch() 函數(shù)
loggerMiddleware // 一個很便捷的 middleware,用來打印 action 日志
)
)
export default store;
由上面代碼可以看出,多了一個ajaxReducer.js,在這里,我單獨(dú)用一個reducer專門來處理ajax請求。并且由于考慮到后期可能需要用到中斷請求和進(jìn)度展示,暫時不考慮使用fetch。
// 具體數(shù)據(jù)緩存,加載菊花的展示這些細(xì)節(jié)和優(yōu)化方面暫不考慮
const initState = {
isFetching: false, // 是否加載中
items: []
};
function ajaxReducer(state = initState, action) {
console.log(state, action, 5566)
switch (action.type) {
case 'AJAX_START':
return Object.assign({}, state, {
isFetching : true
});
case 'AJAX_SUCCESS':
return Object.assign({}, state, {
isFetching : false,
items: action.data
});
case 'AJAX_FAIL':
return Object.assign({}, state, {
isFetching : false,
items: []
});
default:
return Object.assign({}, state);
}
}
export function getData(params) {
return (dispatch, getState) => {
console.log("ajax請求數(shù)據(jù)")
dispatch({type: "AJAX_START"});
return $.ajax({
url: '/json',
type: "GET",
data: {funcNo: "100000", id: 1},
success: (json) => {
return dispatch({type: "AJAX_SUCCESS", data: json.data});
},
error: (json) => {
alert(json.info);
return dispatch({type: "AJAX_FAIL"});
}
})
}
}
export default ajaxReducer;
這樣以來就可以直接在頁面調(diào)用
// 如在index.jsx上
import { getData } from '../actions/ajaxReducer';
import store from '../actions/reducer'; // 引用store和thunk后,可直接省去mapDispatchToProps,mapStateToProps
componentWillMount() {
store.dispatch(getData()).then(() => {
// 成功獲取數(shù)據(jù)后,可對數(shù)據(jù)進(jìn)行state處理,更新到當(dāng)前的reducer里面的state
console.log(store.getState(), "成功的回調(diào)")
})
}
結(jié)語: 本來下一步是介紹express的,但其配置相對簡單,數(shù)據(jù)庫又是采用連接池鏈接的,邏輯是使用bluebird將所有的模塊Promise化。至于頁面也沒啥好說的,在我的構(gòu)思中,頁面組件化是一個趨勢,未來的頁面猶如拼圖一樣,直接拼接而成,當(dāng)然這前提就需要我們開發(fā)出龐大的組件庫。(好吧,沒人點(diǎn)贊,實(shí)在沒動力寫下去了??)