webpack+express+react系列三(react-redux)

寫到系列三的時候,感覺有點(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í)在沒動力寫下去了??)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容