從零開始React項目架構(三)

前言


這篇咱們來實現mock數據模擬接口,實現先後端分離開發,不用在追着後端大大要接口了~javascript

開發


先來簡單介紹下咱們用到的包吧:java

名稱 簡介
koa 一種簡單好用的 Web 框架。它的特色是優雅、簡潔、表達力強、自由度高。
koa-router 經常使用的 koa 的路由庫
koa-bodyparser 用來解析body的中間件
mockjs 生成隨機數據,模擬數據

讓咱們來安裝這些包吧node

npm install -D koa koa-router koa-bodyparser mockjs
複製代碼

還記得咱們上一張的目錄結構吧,讓咱們在mock文件夾下建立mock-server.jsreact

const Koa = require('koa');
const router = require('koa-router')();
const bodyParser = require('koa-bodyparser');
const app = new Koa();

app.use(bodyParser())


const home = require('./home')

app.use(router.routes())
router.get('/list', async(ctx, next) => {
  ctx.body = home
  await next()
})

// error-handling
app.on('error', (err, ctx) => {
    console.error('server error', err, ctx)
});

app.listen(3000);  //這裏的端口要和webpack裏devServer的端口對應
console.log('app started at port 3000')
複製代碼

注意哦, app.listen的端口要和webpack裏devServer的端口對應webpack

而後咱們在建立一個home.js, 讓咱們先使用mock文檔給的例子程序員

// 使用 Mock
var Mock = require('mockjs')
var data = Mock.mock( {
    // 屬性 list 的值是一個數組,其中含有 1 到 10 個元素
    'list|1-10': [{
        // 屬性 id 是一個自增數,起始值爲 1,每次增 1
        'id|+1': 1
    }]
})

module.exports =  data
複製代碼

而後咱們在package.json的 scripts 添加啓動命令web

"mock": "node ./mock/mock-server.js"
複製代碼

好,讓咱們執行命令行執行npm run mock, 看到命令行輸出app started at port 3000 說明啓動成功了。咱們能夠發起請求來看看是否是咱們home.js裏面模擬數據。shell

我使用的是fetch來發起請求,固然辦法有不少不必定要和我同樣npm

npm install whatwg-fetch -S
複製代碼

讓咱們在Home.jsx裏面加入一些代碼:json

import React, {PureComponent} from 'react'

export default class Home extends PureComponent{
    componentDidMount(){
        fetch('api/list')
    }
    render(){
        return (
            <div>Hello World!</div>
        )
    }
}
複製代碼

好了,如今讓咱們在新的命令行裏面執行npm start運行項目。在瀏覽器查看是否有list請求發出和返回數據。

如今讓咱們修改home.js模擬數據,咱們會發現請求的返回數據沒有改變,須要重啓koa的服務才能夠,很麻煩,不是咱們想要的,那怎麼辦呢? 讓我安裝nodemon來解決吧

npm install -D nodemon
複製代碼

修改mock 命令爲

"mock": "nodemon ./mock/mock-server.js"
複製代碼

運行npm run mock後,在修改home.js的模擬數據試試,是否是很完美。

好了,mock數據模擬接口咱們已經實現了,不過咱們的項目有點簡單,讓咱們來加幾個頁面,順便把fetch封裝下吧。 在utils文件夾下,建立request.js

import 'whatwg-fetch'

const codeMessage = {
    200: '服務器成功返回請求的數據。',
    201: '新建或修改數據成功。',
    202: '一個請求已經進入後臺排隊(異步任務)。',
    204: '刪除數據成功。',
    400: '發出的請求有錯誤,服務器沒有進行新建或修改數據的操做。',
    401: '用戶沒有權限(令牌、用戶名、密碼錯誤)。',
    403: '用戶獲得受權,可是訪問是被禁止的。',
    404: '發出的請求針對的是不存在的記錄,服務器沒有進行操做。',
    406: '請求的格式不可得。',
    410: '請求的資源被永久刪除,且不會再獲得的。',
    422: '當建立一個對象時,發生一個驗證錯誤。',
    500: '服務器發生錯誤,請檢查服務器。',
    502: '網關錯誤。',
    503: '服務不可用,服務器暫時過載或維護。',
    504: '網關超時。',
}

function checkStatus(response) {
    if (response.status >= 200 && response.status < 300) {
        return response
    }
    const errortext = codeMessage[response.status] || response.statusText
    const error = new Error(errortext)
    error.name = response.status
    error.response = response
    throw error
}

/** * * @param {string} url 請求url * @param {object} [options] fetch 配置選項 * @return {object} */
export default function request(url, options) {
    const defaultOptions = {
        credentials: 'include',
    }
    const newOptions = { ...defaultOptions, ...options }
    if (newOptions.method === 'POST' || newOptions.method === 'PUT') {
        if (!(newOptions.body instanceof FormData)) {
            newOptions.headers = {
                Accept: 'application/json',
                'Content-Type': 'application/json; charset=utf-8',
                ...newOptions.headers,
            }
            newOptions.body = JSON.stringify(newOptions.body)
        } else {
            newOptions.headers = {
                Accept: 'application/json',
                ...newOptions.headers,
            }
        }
    }

    return fetch(url, newOptions)
        .then(checkStatus)
        .then(response => {
            if (newOptions.method === 'DELETE' || response.status === 204) {
                return response.text()
            }
            return response.json()
        })
        .catch(e => {
            const status = e.name
            console.log(status)
        })
}
複製代碼

這裏借鑑了antd-pro的fetch封裝。

routes文件夾下,建立Page1.jsx Page2.jsx Page3.jsx 修改Home.jsx

import React, { PureComponent } from 'react'
import { Route, Switch, Redirect, Link } from 'react-router-dom'

import Page1 from './Page1.jsx'
import Page2 from './Page2.jsx'
import Page3 from './Page3.jsx'

export default class Home extends PureComponent{

    render(){
        return (
            <div>
                <div style={styles.container}>
                    <Link to="/page1" style={styles.link} >Page1</Link>
                    <Link to="/page2" style={styles.link} >Page2</Link>
                    <Link to="/page3">Page3</Link>
                </div>
                <div style={styles.container}>
                    <Switch>
                        <Route  path="/page1" component={Page1}/>
                        <Route  path="/page2" component={Page2}/>
                        <Route  path="/page3" component={Page3}/>

                        <Redirect exact from="/" to='/page1' />
                    </Switch>
                </div>
                
            </div>
        )
    }
}

const styles = {
    container: {
        display: 'flex',
        justifyContent: 'center'
    },
    link: {
        marginRight: 10
    }
}
複製代碼

Page1.jsx爲:

import React, {PureComponent} from 'react'
import request from '../utils/request'

export default class Page1 extends PureComponent{
    state={
        data: {}
    }
    click=()=>{
        request('api/page1').then(data=>{
            this.setState({
                data: data
            })
        })
    }
    render(){
        return (
            <div> <h1>Hello Page1!</h1> <input type="button" value="獲取mcok" onClick={this.click} /> <div style={{width:300}}> <p>Page1的mock模擬數據爲:{JSON.stringify(this.state.data)}</p> </div> </div> ) } } 複製代碼

Page2.jsx爲:

import React, {PureComponent} from 'react'
import request from '../utils/request'

export default class Page2 extends PureComponent{
    state={
        data: {}
    }
    click=()=>{
        request('api/page2').then(data=>{
            this.setState({
                data: data
            })
        })
    }
    render(){
        return (
            <div> <h1>Hello Page2!</h1> <input type="button" value="獲取mcok" onClick={this.click} /> <div style={{width:300}}> <p>Page2的mock模擬數據爲:{JSON.stringify(this.state.data)}</p> </div> </div> ) } } 複製代碼

Page3.jsx爲:

import React, {PureComponent} from 'react'
import request from '../utils/request'

export default class Page2 extends PureComponent{
    state={
        data: {}        
    }
    click=()=>{
        request('api/page3').then(data=>{
            this.setState({
                data: data
            })
        })
    }
    render(){
        return (
            <div> <h1>Hello Page3!</h1> <input type="button" value="獲取mcok" onClick={this.click} /> <div style={{width:300}}> <p>Page3的mock模擬數據爲:{JSON.stringify(this.state.data)}</p> </div> </div> ) } } 複製代碼

修改 mock文件夾下的home.jsmock.js

const router = require('koa-router')();
var Mock = require('mockjs')

router.get('/page1', async (ctx, next) => {
    ctx.body = Mock.mock({
        data: [
            {
                "id": 1,
                "title": "科學搬磚組",
                "description": '那是一種內在的東西,他們到達不了,也沒法觸及的',
            },
            {
                "id": 2,
                "title": "程序員平常",
                "description": '那時候我只會想本身想要什麼,從不想本身擁有什麼',
            },
            {
                "id": 3,
                "title": "騙你來學計算機",
                "description": '生命就像一盒巧克力,結果每每出人意料',
            },
            {
                "id": 4,
                "title": "全組都是吳彥祖",
                "description": '但願是一個好東西,也許是最好的,好東西是不會消亡的',
            },
        ],
        status: 200,
    })
})

router.get('/page2', async (ctx, next) => {
    ctx.body = Mock.mock({
        "data|1-10":[{
            'id|+1': 1,
            'number|1-100': 10.0
        }]
    })
})

router.get('/page3', async (ctx, next) => {
    ctx.body = Mock.mock({
        "data|1-10":[{
            'id|+1': 1,
            'email': '@email',
            'name' : '@Name'
        }]
    })
})

module.exports = router
複製代碼

mock-server.js修改成

const Koa = require('koa');
const router = require('koa-router')();
const bodyParser = require('koa-bodyparser');
const app = new Koa();
const mock = require('./mock');

app.use(bodyParser())

app.use(mock.routes())

// error-handling
app.on('error', (err, ctx) => {
    console.error('server error', err, ctx)
});

app.listen(3000);
console.log('app started at port 3000')
複製代碼

好,如今咱們在跑起來看看吧。page1的數據是不變的,page2和page3的數據每次請求都是隨機生成的。

是否是感受每次要命令行啓動兩次很麻煩呢?咱們固然能夠整合成一個命令來啓動

npm install concurrently -D
複製代碼

而後咱們在package.json的 scripts 添加新啓動命令

"dev": "concurrently \"npm run mock\" \"npm start\" "
複製代碼

如今咱們就可使用npm run dev 一個命令來啓動了。

總結


本章主要介紹實現mock本地數據模擬接口,並對項目增長了些簡單頁面。

下篇文章咱們來介紹開發環境webpack的配置

系列文章


  1. 從零開始React項目架構(一)
  2. 從零開始React項目架構(二)
  3. 從零開始React項目架構(四)
相關文章
相關標籤/搜索