阅读 3494

从零开始React项目架构(三)

前言


这篇我们来实现mock数据模拟接口,实现前后端分离开发,不用在追着后端大大要接口了~

开发


先来简单介绍下我们用到的包吧:

名称 简介
koa 一种简单好用的 Web 框架。它的特点是优雅、简洁、表达力强、自由度高。
koa-router 常用的 koa 的路由库
koa-bodyparser 用来解析body的中间件
mockjs 生成随机数据,模拟数据

让我们来安装这些包吧

npm install -D koa koa-router koa-bodyparser mockjs
复制代码

还记得我们上一张的目录结构吧,让我们在mock文件夹下创建mock-server.js

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的端口对应

然后我们在创建一个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 添加启动命令

"mock": "node ./mock/mock-server.js"
复制代码

好,让我们执行命令行执行npm run mock, 看到命令行输出app started at port 3000 说明启动成功了。我们可以发起请求来看看是不是我们home.js里面模拟数据。

我使用的是fetch来发起请求,当然办法有很多不一定要和我一样

npm install whatwg-fetch -S
复制代码

让我们在Home.jsx里面加入一些代码:

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项目架构(四)
文章分类
前端