React開發管理後臺實踐2---React基本內容學習

最老程式設計師閆濤發表於2018-09-01

在這一節裡,我們以一個醫生管理自己的患者為例,向大家介紹React基本內容。包括React、Redux和Router,以及基於Ajax的前後端聯調功能。

頁面元件顯示

假設我們頁面顯示如下內容,標題是“患者管理”,第一個列表標題為“高血壓患者”,然後是高血壓患者列表,第二個列表標題為“糖尿病患者”,然後是糖尿病患者列表。
在程式入口點src/App.js中新增如下程式碼:

import React, { Component } from 'react';
import HighBlood from './HighBlood';
import Diabete from './Diabete';
const diabetes = [
    {'patientId': 201, 'patientName': 'D201'},
    {'patientId': 202, 'patientName': 'D202'},
    {'patientId': 203, 'patientName': 'D203'},
    {'patientId': 204, 'patientName': 'D204'}
];
class App extends Component {
    render() {
        const title = '患者管理';
        return (
            <div>
                <h1>{title}</h1>
                <HighBlood></HighBlood>
                <Diabete patients={diabetes}></Diabete>
            </div>
        );
    }
}
//......

其中HighBlood和Diabete是兩個子元件,分別定義在src/HighBlood.js和src/Diabete.js中。我們將糖尿病患者,我們使用全域性變數陣列diabetes來儲存,並通過patients屬性傳遞給Diabete子元件。而高血壓患者列表資料將定義在HighBlood元件內部的state中。
我們首先來看高血壓元件src/HighBlood.js:

import React, { Component } from 'react';

class HighBlood extends Component {
    constructor(props) {
        super(props);
        this.state = {
            ps: [
                {'patientId': 101, 'patientName': 'p101'},
                {'patientId': 102, 'patientName': 'p102'},
                {'patientId': 103, 'patientName': 'p103'},
                {'patientId': 104, 'patientName': 'p104'}
            ]
        };
    }

    addPatient = () => {
        console.log('新增高血壓患者');
        this.setState({
            ps: [...this.state.ps, {'patientId': 105, 'patientName': 'p105!'}
            ]
        });
    }

    render() {
        const content = '高血壓患者';
        return (
            <div>
                <h2>{content}</h2>
                <button onClick={this.addPatient}>新增患者</button>
                <ul>
                    {this.state.ps.map(v => {
                        return <li key={v.patientId}>{v.patientName}</li>
                    })}
                </ul>
            </div>
        );
    }
}

export default HighBlood;

我們通過建構函式中定義state中具有一個ps屬性,是一個物件陣列,儲存患者列表資料。與糖尿病患者元件不同,採用state定義的屬性可以動態更加,也就是介面中的“新增患者”按鈕是可以使用的。
接著我們定義了一個“新增患者”按鈕,並定義單擊響應函式為addPatient,注意這裡addPatient函式定義時只能採用箭頭函式形式,否則會報找不到this的錯誤(當然也可以有其他方法來避免這個錯誤,大家只需要掌握這種就可以了)。
接下來我們來看render函式,其首先顯示了列表標題。然後定義ul元素,state中的陣列ps呼叫map函式,在其內部用箭頭函式定義了對陣列每個元素的操作。這裡是利用陣列元素的值建立一個li元素。
如果我們點選新增按鈕,就會執行addPatient函式,在這個函式中,我們通過this.setState來重新給ps陣列賦值,在賦值時我們先用展開運算子,將原陣列內容展開為列表,然後再新增新元素。
接下來我們來看Diabete元件,這個元件與HighBlood的唯一區別就是患者列表資料是通過父元件以屬性方式傳遞過來的。這裡需要注意的一點,由於患者列表是父元件通過屬性傳遞過來的,因此患者列表是不能改變的,所以這裡的新增按鈕是不起作用的,程式碼如下所示:

import React, { Component } from 'react';

class Diabete extends Component {
    addPatient = () => {
        console.log('新增糖尿病患者');
    }

    componentWillMount() {
        console.log('生命週期函式:componentWillMount');
    }

    componentDidMount() {
        console.log('生命週期函式:componentDidMount ^_^');
    }

    render() {
        const content = '糖尿病患者';
        return (
            <div>
                <h2>{content}</h2>
                <button onClick={this.addPatient}>新增患者</button>
                <ul>
                    {this.props.patients.map(v => {
                        return <li key={v.patientId}>{v.patientName}</li>;
                    })}
                </ul>
            </div>
        );
    }
}

export default Diabete;

Redux初步

利用Redux管理患教文章數量

首先安裝redux為:

npm install redux --save

我們首先定義事件型別,在患教文章管理中,我們有兩個事件,一個是新增患教文章,一個刪除患教文章,我們在src/action_type.js定義這兩個事件:

export const ADD_ARTICLE = 'AddArticle'
export const REMOVE_ARTICLE = 'RemoveArticle'

接著我們定義產生這兩個事件的方法,在src/action.js檔案中定義:

import { ADD_ARTICLE, REMOVE_ARTICLE } from './action_type'

export function createAddArticleAction() {
    let action = { type: ADD_ARTICLE }
    return action
}

export function createRemoveArticleAction() {
    let action = { type: REMOVE_ARTICLE }
    return action
}

接著我們定義reducer來處理這兩個事件,如src/reducer.js檔案所示:

// Reducer定義
import { ADD_ARTICLE, REMOVE_ARTICLE } from './action_type'

export function articleReducer(state={}, action) {
    console.log(`articleReducer type=${action.type}...`)
    switch (action.type) {
        case ADD_ARTICLE:
            if (!state.articleNum) {
                state.articleNum = 0
            }
            state.articleNum += 1
            console.log(`articleReducer articleNum=${state.articleNum}...`)
            return state
        case REMOVE_ARTICLE:
            if (!state.articleNum) {
                state.articleNum = 0
            }
            state.articleNum -= 1
            return state
        default:
            return state
    }
}

我們首先在src/index.js檔案中引入redux的store物件,並進行監聽:

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux'
import './index.css'
import App from './App'
import registerServiceWorker from './registerServiceWorker'
import { articleReducer } from './reducer'

const store = createStore(articleReducer)
function render() {
    ReactDOM.render(<App store={store} />, document.getElementById('root'))
}
render()
registerServiceWorker()
store.subscribe(render)

最後我們在頁面中新增增加文章和刪除文章按鈕,並新增相應的事件處理函式,如下所示:

import React, { Component } from 'react'
import HighBlood from './HighBlood'
import Diabete from './Diabete'
import { createAddArticleAction, createRemoveArticleAction } from './action'

const diabetes = [
    {'patientId': 201, 'patientName': 'D201'},
    {'patientId': 202, 'patientName': 'D202'},
    {'patientId': 203, 'patientName': 'D203'},
    {'patientId': 204, 'patientName': 'D204'}
]

class App extends Component {
    addArticle = () => {
        console.log('App.addArticle')
        const store = this.props.store
        store.dispatch(createAddArticleAction())
    }
    removeArticle = () => {
        console.log('App.removeArticle')
        const store = this.props.store
        store.dispatch(createRemoveArticleAction())
    }

    render() {
        const title = '患者管理'
        const store = this.props.store.getState()       
        let num = 0
        if (store.articleNum) {
            num = store.articleNum
        }
        return (
            <div>
                <h1>{title}</h1>
                <span>文章數:{num}</span><br />
                <button onClick={this.addArticle}>新增文章</button><br />
                <button onClick={this.removeArticle}>刪除文章</button>
                <HighBlood></HighBlood>
                <Diabete patients={diabetes}></Diabete>
            </div>
        )
    }
}

export default App

由上面的程式碼可以看到,在render方法中,我們通過屬性獲取store中的state,可以得到文章數量並顯示在介面中。在新增或刪除文章時,我們生成相應的事件,通過store.dispatch方法,最終呼叫reducer中相應的處理函式來進行處理。
為了使程式更加優雅,我們可以將事件生成函式引用移出App元件。我們在index.js中引入事件,並以屬性的形式傳遞給App元件,如下所示:

......

import { articleReducer } from './reducer'
import { createAddArticleAction, createRemoveArticleAction } from './action'

const store = createStore(articleReducer)
function render() {
    ReactDOM.render(<App store={store} createAddArticleAction={createAddArticleAction} createRemoveArticleAction={createRemoveArticleAction} />, document.getElementById('root'))
}
......

我們在App元件中,可以通過屬性來使用這些函式,如下所示:

......
class App extends Component {
    addArticle = () => {
        console.log('App.addArticle')
        const store = this.props.store
        const createAddArticleAction = this.props.createAddArticleAction
        store.dispatch(createAddArticleAction())
    }
    removeArticle = () => {
        console.log('App.removeArticle')
        const store = this.props.store
        const createRemoveArticleAction = this.props.createRemoveArticleAction
        store.dispatch(createRemoveArticleAction())
    }

    render() {
......

Redux處理非同步任務

我們首先來安裝一個三方庫:

npm install redux-thunk --save

開啟非同步中介軟體,在index.js檔案中引入thunk和中介軟體:

......
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import './index.css'
import App from './App'
......

const store = createStore(articleReducer, applyMiddleware(thunk))
function render() {
    ReactDOM.render(<App store={store} createAddArticleAction={createAddArticleAction} createRemoveArticleAction={createRemoveArticleAction} createAddArticleAsyncAction={createAddArticleAsyncAction} />, document.getElementById('root'))
}
......

我們在第3行引入redux的applyMiddleware,在第4行引入非同步處理中介軟體thunk,在建立store時加入中介軟體初始化,並將非同步事件createAddArticleAsyncAction以屬性形式傳遞給App元件。
在App元件中,我們新增一個非同步新增按鈕,如下所示:

    addArticleAsync = () => {
        console.log('App.addArticleAsync...........')
        const store = this.props.store
        const createAddArticleAsyncAction = this.props.createAddArticleAsyncAction
        store.dispatch(createAddArticleAsyncAction())
    }

其實這部分程式碼與同步事件基本沒有什麼區別。
在src/action.js檔案中新增非同步事件,如下所示:

export function createAddArticleAsyncAction() {
    return dispatch => {
        setTimeout( () => { dispatch(createAddArticleAction()) }, 2000)
    }
}

在非同步處理函式中,我們返回的是一個箭頭函式,引數為dispatch,其功能就是呼叫延時函式,在2秒後呼叫dispatch函式,執行真正的新增操作。

Redux開發除錯工具

為開發Redux應用方便,可以安裝redux的除錯工具,開啟Chrome的“更多工具”=》“擴充套件程式”,點選左上角選單,點選最下面“開啟Chrome網上應用商店”,在其中搜尋Redux DevTools,安裝該外掛。
啟用Redux DevTools需要在src/index.js檔案中新增如下程式碼:

......
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
......
const store = createStore(articleReducer, 
    compose(
        applyMiddleware(thunk),
        window.devToolsExtension ? window.devToolsExtension() : f=>f
    )
)
......

首先引入redux中的compose函式,接著在建立store時使用compose函式啟用開發除錯工具。

使用react-redux

直接使用redux還是相對比較煩瑣,我們可以使用react-redux來簡化這一過程,通過如下命令安裝:

npm install react-redux --save

我們在src/index.js檔案中引入react-redux的Provider元件,程式碼如下所示:

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
import './index.css'
import App from './App'
import registerServiceWorker from './registerServiceWorker'
import { articleReducer } from './reducer'
import { createAddArticleAction, createRemoveArticleAction, createAddArticleAsyncAction } from './action'

const store = createStore(articleReducer, 
    compose(
        applyMiddleware(thunk),
        window.devToolsExtension ? window.devToolsExtension() : f=>f
    )
)

ReactDOM.render(
        (
            <Provider store={store}>
                <App />
            </Provider>
        ), 
        document.getElementById('root'))
registerServiceWorker()

這裡最大的變化是我們不需要Render函式和監聽了,同時store只需要作為屬性傳遞給Provider元件即可。
接下來我們需要改造App元件,將src/App.js改為如下形式:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createAddArticleAction, createRemoveArticleAction, createAddArticleAsyncAction } from './action'
import HighBlood from './HighBlood'
import Diabete from './Diabete'

const diabetes = [
    {'patientId': 201, 'patientName': 'D201'},
    {'patientId': 202, 'patientName': 'D202'},
    {'patientId': 203, 'patientName': 'D203'},
    {'patientId': 204, 'patientName': 'D204'}
]

class App extends Component {
    render() {
        const title = '患者管理'
        const store = this.props.store
        const state = store.getState()
        return (
            <div>
                <h1>{title}</h1>
                <span>文章數:{this.props.articleNum}</span><br />
                <button onClick={this.props.createAddArticleAction}>新增文章</button><br />
                <button onClick={this.props.createRemoveArticleAction}>刪除文章</button>
                <button onClick={this.props.createAddArticleAsyncAction}>非同步新增</button>
                <HighBlood></HighBlood>
                <Diabete patients={diabetes}></Diabete>
            </div>
        )
    }
}

const mapStatetoProps = (state) => {
    return { articleNum: state.articleNum }
}
const actionCreators = { createAddArticleAction, createRemoveArticleAction, createAddArticleAsyncAction }
App = connect(mapStatetoProps, actionCreators)(App)

export default App

大家可以看到,我們在App元件中,就不需要使用dispatch了,直接使用屬性中對應的方法即可。在最後面,我們用mapStatetoProps這時狀態中變數對應於本元件中的屬性,actionCreators定義事件生成函式為元件屬性。
接下來我們安裝babel外掛:

npm install babel-plugin-transform-decorators-legacy --save

然後在package.json檔案的babel定義部分新增外掛,如下所示:

  "babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
        "transform-decorators-legacy"
    ]
  },

如上所示,加入了transform-decorators-legacy外掛。
接著我們就可以簡化App元件中的程式碼了,如下所示:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createAddArticleAction, createRemoveArticleAction, createAddArticleAsyncAction } from './action'
import HighBlood from './HighBlood'
import Diabete from './Diabete'

const diabetes = [
    {'patientId': 201, 'patientName': 'D201'},
    {'patientId': 202, 'patientName': 'D202'},
    {'patientId': 203, 'patientName': 'D203'},
    {'patientId': 204, 'patientName': 'D204'}
]

@connect(
    (state) => {
        return { articleNum: state.articleNum, state: state }
    },
    { createAddArticleAction, createRemoveArticleAction, createAddArticleAsyncAction }
)
class App extends Component {
    render() {
        const title = '患者管理 v0.0.2'
        console.log(`num=${this.props.articleNum}`)
        return (
            <div>
                <h1>{title}</h1>
                <span>文章數:{this.props.articleNum} vs {this.props.state.articleNum}</span><br />
                <button onClick={this.props.createAddArticleAction}>新增文章</button><br />
                <button onClick={this.props.createRemoveArticleAction}>刪除文章</button>
                <button onClick={this.props.createAddArticleAsyncAction}>非同步新增</button>
                <HighBlood></HighBlood>
                <Diabete patients={diabetes}></Diabete>
            </div>
        )
    }
}

export default App

通過使用@connect語法,可以精簡react-redux繫結程式碼,該函式有兩個引數,第一個引數是屬性中需要state的屬性,第二個引數是需要自動dispatch的事件。

使用Router

Router 4是最新版本的路由元件,可以在Web、ReactNative上統一使用。首先是先安裝庫:

npm install react-router-dom --save

我們首先在src/index.js檔案中定義Router,如下所示:

......
import { Provider } from 'react-redux'
import { BrowserRouter, Route, Link } from 'react-router-dom'
import './index.css'
......
ReactDOM.render(
    (
        <Provider store={store}>
            <BrowserRouter>
                <div>
                    <ul>
                        <li><Link to='/'>患者列表</Link></li>
                        <li><Link to='/patientDetail'>患者詳情</Link></li>
                        <li><Link to='/articleList'>文章列表</Link></li>
                    </ul>
                    <Route path='/' exact component={App}></Route>
                    <Route path='/patientDetail' component={PatientDetail}></Route>
                    <Route path='/articleList' component={ArticleList}></Route>
                </div>
            </BrowserRouter>
        </Provider>
    ), 
    document.getElementById('root')
)
......

在第2行引入router中常用元件。在頁面上方定義三個導航選單,分別對應三個頁面。通過不同的URL顯示不同的元件。
然後再分別定義PatientDetail元件:

import React, { Component } from 'react';

class PatientDetail extends Component {
    render() {
        return <h1>患者詳情</h1>
    }
}

export default PatientDetail

再定義文章列表頁面:

import React, { Component } from 'react';

class ArticleList extends Component {
    render() {
        return <h1>文章列表</h1>
    }
}

export default ArticleList

這樣就組成了一個最簡單的多頁面應用。由此可見Router功能的強大。在後面我們要用到的開源後臺應用,就是採用這一基本原理來實現的。

迷你完整應用

我們要實現的功能是使用者在訪問任意一個頁面時,如果沒有登入userId<=0,則跳轉到登入頁面,如果已經登入,顯示使用者名稱和登出按鈕,點選登出按鈕回到登入頁面。在登入頁面點選登入按鈕,進入患者列表頁面,如果訪問不存在的頁面,顯示404頁面。我們還有一個關於我們頁面,使用者不登入也可以看。
為了開發這一應用,我們現在有兩個主要的功能,一個是登入相關邏輯,另一個是患者列表頁面邏輯。為此我們分別設計Auth.redux.js和Home.redux.js兩個reducer。因為在我們的體系架構下,只能有一個reducer,所以我們需要使用combineReducers元件。我們新建src/reducers.js檔案,內容如下所示:

// 合併所有reducers
import { combineReducers } from 'redux'
import { authReducer } from './Auth.redux'
import { articleReducer } from './Home.redux'

export default combineReducers({
    authReducer,
    articleReducer
})

我們在程式入口處的src/index.js中,生成全域性store時,引用這個合併後的reducers,程式碼如下所示:

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom'
import './index.css'
import registerServiceWorker from './registerServiceWorker'
import reducers from './reducers'
import App from './App'
import Login from './Login'
import NotFound from './NotFound'

const store = createStore(reducers, 
    compose(
        applyMiddleware(thunk),
        window.devToolsExtension ? window.devToolsExtension() : f=>f
    )
)


ReactDOM.render(
    (
        <Provider store={store}>
            <BrowserRouter>
                <Switch>
                    <Route path='/login' component={Login}></Route>
                    <Route path='/home' component={App}></Route>
                    <Route path='/not-found' component={NotFound}></Route>
                    <Redirect to='/not-found'></Redirect>
                </Switch>
            </BrowserRouter>
        </Provider>
    ), 
    document.getElementById('root')
)
registerServiceWorker()

在上面的程式碼中,我們在createStore時傳入的是我們剛剛建立的統一的reducer,其中包括articleReducer和authReducer兩個元素。接下來路由方面,我們定義了登入頁Login,首頁為App元件,404頁面,並定義如果沒有任何路由命中,則直接轉到404頁面。
我們先來看登入頁面src/Login.js定義:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import { login } from './Auth.redux'

@connect(
    (state) => {
        return {
            userId: state.authReducer.userId
        }
    },
    { login }
)
class Login extends Component {
    constructor(props) {
        super(props)
    }

    render() {
        const redirectToPage = <Redirect to='/home'></Redirect>
        const page = (
            <div>
                <h2>請登入系統</h2>
                <button onClick={this.props.login}>登入</button>
            </div>
        )
        return this.props.userId > 0 ? redirectToPage : page
    }
}

export default Login

我們首先將state.authReducer.userId取過來作為屬性,頁面內容有兩種可能性,如果userId<=0則顯示登入頁面,userId>0則顯示首頁(患者列表頁面)。其中的登入事件定義在src/Auth.redux.js中,如下所示:

// 用於登入、登出、註冊
// 定義事件型別
export const LOGIN = 'login'
export const LOGOUT = 'logout'

// 定義reducer
export function authReducer(state={userId: 0, userName: '遊客'}, action) {
    switch (action.type) {
        case LOGIN:
            return { ...state, userId: 100, userName: '王一' }
        case LOGOUT:
            return  { ...state, userId: 0, userName: '遊客' }
        default:
            return state
    }
}

// 定義事件生成函式
export function login() {
    let action = { type: LOGIN }
    return action
}

export function logout() {
    let action = { type: LOGOUT }
    return action
}

接下來我們來看由App元件定義的導航頁面src/App.js,如下所示:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route, Link } from 'react-router-dom'
import { logout } from './Auth.redux'
import Home from './Home'
import PatientDetail from './PatientDetail'
import ArticleList from './ArticleList'

@connect(
    (state) => {
        return {
            userId: state.authReducer.userId,
            userName: state.authReducer.userName,
        }
    },
    { logout }
)
class App extends Component {
    constructor(props) {
        super(props)
    }

    render() {
        return (
            <div>
                <h2>使用者:{this.props.userName}</h2>
                <button onClick={this.props.logout}>登出</button>
                <ul>
                    <li><Link to='/home'>患者列表</Link></li>
                    <li><Link to='/home/patientDetail'>患者詳情</Link></li>
                    <li><Link to='/home/articleList'>文章列表</Link></li>
                </ul>
                <Route path='/home' exact component={Home}></Route>
                <Route path='/home/patientDetail' component={PatientDetail}></Route>
                <Route path='/home/articleList' component={ArticleList}></Route>
            </div>
        )
    }
}

export default App

我們在頁面首部顯示使用者名稱,然後是登出按鈕,然後就是導航連結。我們在這裡假設Home元件代表的患者列表頁面,需要使用者登入後才能看,其他頁面則不需要。
我們接下來看患者列表頁面src/Home.js,如下所示:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route, Link, Redirect } from 'react-router-dom'
import { articleReducer, addArticle, removeArticle, addArticleAsync } from './Home.redux'
import HighBlood from './HighBlood'
import Diabete from './Diabete'
import PatientDetail from './PatientDetail'
import ArticleList from './ArticleList'

const diabetes = [
    {'patientId': 201, 'patientName': 'D201'},
    {'patientId': 202, 'patientName': 'D202'},
    {'patientId': 203, 'patientName': 'D203'},
    {'patientId': 204, 'patientName': 'D204'}
]

@connect(
    (state) => {
        return { 
            articleNum: state.articleReducer.articleNum,
            userId: state.authReducer.userId
        }
    },
    { addArticle, removeArticle, addArticleAsync }
)
class Home extends Component {
    constructor(props) {
        super(props)
    }

    render() {
        const title = '患者管理 v0.0.2'
        const redirectToLogin = <Redirect to='/login'></Redirect>
        const page = (
            <div>
                <h1>{title}</h1>
                <span>文章數:{this.props.articleNum}</span><br />
                <button onClick={this.props.addArticle}>新增文章</button><br />
                <button onClick={this.props.removeArticle}>刪除文章</button>
                <button onClick={this.props.addArticleAsync}>非同步新增</button>
                <HighBlood></HighBlood>
                <Diabete patients={diabetes}></Diabete>
            </div>
        )
        return this.props.userId<=0 ? redirectToLogin : page
    }
}

export default Home

這裡也將state.authReducer.userId取出來作為屬性,也分為兩種情況,一種是userId>0,此時顯示患者列表頁面,另一種是userId<=0,則跳轉到登入頁面。
Home元件所對應的Reducer為src/Home.redux.js,如下所示:

// 定義事件型別
export const ADD_ARTICLE = 'AddArticle'
export const REMOVE_ARTICLE = 'RemoveArticle'

// 定義reducer
export function articleReducer(state={}, action) {
    if (!state.articleNum) {
        state.articleNum = 0
    }
    switch (action.type) {
        case ADD_ARTICLE:
            return { ...state, articleNum: state.articleNum+1 }
        case REMOVE_ARTICLE:
            return  { ...state, articleNum: state.articleNum-1 }
        default:
            return state
    }
}

// 定義事件生成函式
export function addArticle() {
    let action = { type: ADD_ARTICLE }
    return action
}

export function removeArticle() {
    let action = { type: REMOVE_ARTICLE }
    return action
}

export function addArticleAsync() {
    return dispatch => {
        setTimeout( () => { dispatch(addArticle()) }, 2000)
    }
}

登入功能前後端聯調

我們首先安裝Ajax支援庫:

npm install axios --save

因為我們的開發伺服器監聽在3000埠,我們伺服器的埠為8090,所以直接使用axios傳送Ajax請求會出現跨域問題,我們需要在package.json配置代理,將所有請求重定向到8090埠上去,如下所示:

......
  "eslintConfig": {
    "extends": "react-app"
  },
  "proxy": "http://localhost:8443"
}

我們來看登入相關功能,在src/Auth.redux.js檔案中,此時login事件就變成向伺服器端傳送請求,就變為非同步事件處理了,如下所示:

// 用於登入、登出、註冊
import axios from 'axios'

// 定義事件型別
export const LOGIN = 'login'
export const LOGOUT = 'logout'
export const ON_LOGIN = 'onLoginRst'

const initState = {
    userId: 0,
    userName: '遊客',
    roleId: 1,
    roleName: '醫生'
}

// 定義reducer
export function authReducer(state=initState, action) {
    switch (action.type) {
        case LOGIN:
            return { ...state, userId: 100, userName: '王一' }
        case LOGOUT:
            return  { ...state, userId: 0, userName: '遊客' }
        case ON_LOGIN:
            let data = action.data
            return { ...state, userId: data.userId, userName: data.userName}
        default:
            return state
    }
}

// 定義事件生成函式
export function login() {
    return dispatch => {
        axios.get('/loginReactLearnUserAjax?loginName=1350&loginPwd=123').then(
            res => {
                if (200 == res.status) {
                    dispatch(onLoginRst(res.data))
                }
            }
        )
    }
}
export function onLoginRst(data) {
    return { type: ON_LOGIN, data: data}
}

export function logout() {
    let action = { type: LOGOUT }
    return action
}

我們伺服器端使用node.js,處理這個請求首先在wkys/index.js中新增路由:

......
// React測試
var reactHdlr = require('./controller/c_react.js');
......
// React學習測試
handlers['/loginReactLearnUserAjax'] = reactHdlr.loginReactLearnUserAjax;

具體的請求處理函式在controller/c_react.js檔案中,如下所示:

/**
*
*/
var url = require("url");
var qs = require("querystring");
var fs = require("fs");
var exec = require("child_process").exec;
var appGlobal = require("../app_global.js");
var baseController = require('../controller/c_base_controller.js');
var db = require("../model/m_mysql.js");

function loginReactLearnUserAjax(request, response) {
    var params = baseController.getRequestParams(request);
    var postParams = request.dataObj;
    var loginName = params.loginName;
    var loginPwd = params.loginPwd;
    var obj = new Object();
    obj.status = 'Ok';
    obj.userId = 201;
    obj.userName = '東方不敗';
    obj.roleId = 108;
    obj.roleName = '超級管理員';
    baseController.sendResponseJson(response, obj);
}

// 對外公共介面定義
exports.loginReactLearnUserAjax = loginReactLearnUserAjax;

小結

至此一個小型應用就基本完成了,由此可以看出,寫一個完整的程式,這方面的工作量還是比較大的,尤其是我們這個小應用中,還沒有考慮頁面的佈局設計,所以做一個React+Router的應用還是有很高的門檻的。幸好有牛人們給我們寫了一些開源框架,使我們可以拿來即用,在下一節中我們將以一個開源React後臺管理框架為基礎,開發我們的後臺管理系統。

相關文章