玩转 Ant Design Pro :Model、Services、Mock 详解

7,161 阅读11分钟

最新最潮最硬核的前端技术,最新最快最好玩的前端资讯!前端、移动、Node全栈一网打尽!

一切都在前端潮客社区公众号,前端潮客的聚集区!

在之前的教程中,小怪鹿已经教大家如何新建antd-pro项目,并在新建的项目中添加自己的项目页面,本文将一步一步向大家演示antd-pro的Mock、Model 、Services 是如何创建的。

引子:这是Ant Design Pro实战系列的第二篇,本教程致力于通过实战开发形式一步步向读者展示这样框架的使用方法,欢迎阅读系列第一篇《ant-design-pro实战篇:快速搭建属于你的项目》

一.新建antd-pro 项目

1.介绍UmiJS

2.安装UmiJS

3.快速搭建项目

4.安装依赖,打开项目

二.新建你的页面

1.新建路由

2.新建页面和UI组件

三.将你的页面与Model连接起来

1.mock功能的使用

2.Services使用

3.定义 Model

4.connect 起来

四.结语

一.新建antd-pro 项目

我们将使用UmiJS来新建antd-pro项目,这也是最新的官方推荐的方式。在这里我们将先了解UmiJS是什么,如何使用UmiJS。

1.介绍UmiJS

umi 是蚂蚁金服的底层前端框架,中文可发音为乌米,是一个可插拔的企业级 react 应用框架。 umi 以路由为基础的,支持约定式路由,以及各种进阶的路由功能,并以此进行功能扩展, 比如支持路由级的按需加载。然后配以完善的插件体系, 覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。

使用antd-pro 之前,开发的同学们最好了解 ES2015+、React、 dvaJS、 antd以及React Router的基础知识,这将为你的开发工作提供必要的基础。

2.安装UmiJS

需要安装与系统相应的node版本,node.js版本>=8.10

npm安装umi

npm install -g umi 

或者使用yarn

yarn global add umi
3.快速搭建项目

新建一个空的文件夹作为项目目录,并在目录下执行:

yarn create umi
或者
npm create umi

选择 ant-design-pro:

 Select the boilerplate type (Use arrow keys)
❯ ant-design-pro  - Create project with an layout-only ant-design-pro boilerplate, use together with umi block.
  app             - Create project with a simple boilerplate, support typescript.
  block           - Create a umi block.
  library         - Create a library with umi.
  plugin          - Create a umi plugin.

然后选择开发的语言以及是否使用最新的ant design版本,Ant Design Pro 脚手架将会自动安装

4.安装依赖,打开项目

先运行npm install命令安装依赖,然后输入npm start 命令,启动完成后会自动打开浏览器访问http://localhost:8000,你看到下面的页面就代表成功了

脚手架已经为我们生成了一个完整的开发框架,提供了涵盖中后台开发的各类功能和坑位,下面是整个项目的目录结构:

├── config                   # umi 配置,包含路由,构建等配置
├── mock                     # 本地模拟数据
├── public
│   └── favicon.png          # Favicon
├── src
│   ├── assets               # 本地静态资源
│   ├── components           # 业务通用组件
│   ├── e2e                  # 集成测试用例
│   ├── layouts              # 通用布局
│   ├── models               # 全局 dva model
│   ├── pages                # 业务页面入口和常用模板
│   ├── services             # 后台接口服务
│   ├── utils                # 工具库
│   ├── locales              # 国际化资源
│   ├── global.less          # 全局样式
│   └── global.ts            # 全局 JS
├── tests                    # 测试工具
├── README.md
└── package.json

二.新建你的页面

要是各位宝藏前端们对如何在ant-design pro里面新建自己的业务页面还不是很熟悉的话,可以看看小怪鹿ant-design pro系列教程的上一篇《ant-design-pro实战篇:快速搭建属于你的项目》

我们用Visual Studio Code编辑器来打开我们的项目,控制台输入:npm start,运行完成打开浏览器对应端口:

1.新建路由

在config/config.js文件中添加新的路由信息:

routes: [
    {
      path: '/user',
      component: '../layouts/UserLayout',
      routes: [
        {
          name: 'login',
          path: '/user/login',
          component: './user/login',
        },
      ],
    },
    {
      path: '/',
      component: '../layouts/SecurityLayout',
      routes: [
        {
          path: '/',
          component: '../layouts/BasicLayout',
          authority: ['admin', 'user'],  //路由权限
          routes: [
            {
              path: '/',
              redirect: '/todoList',  //重定向
            },
            {  
              name: 'todoList',
              icon: 'UnorderedListOutlined',
              path: '/todoList',
              component: './TodoList',  //新添加的路由
            },
            {
              component: './404',
            }
          ],
        },
        {
          component: './404',
        },
      ],
    },
    {
      component: './404',
    },
  ],
2.新建页面和UI组件

我们需要完成一个todolist代办事项的功能,所以我们先分析一下需要哪些组件:首先我们需要一个页面的页头标题,让用户一眼就知道这一页的功能;然后我们需要一个列表展示用户的代办事项;同时我们还需要一个新增代办的功能,打开一个表单来让用户添加他们的代办

(1).编写UI组件

我们首先完成列表组件,我们封装一个简单的列表组件,这是一个函数组件,它接受两个参数:一个事件和一个数据源。

新建pages/TodoList/components/List.js:

import React from 'react';
import PropTypes from 'prop-types';
import { Table, Popconfirm, Button } from 'antd';

const MyList = ({ onDelete, list }) => {
  const columns = [{
    title: '标题',
    dataIndex: 'Title',
    key: 'Title',
  },
  {
    title: '描述',
    dataIndex: 'Description',
    key: 'Description',
  }, {
    title: 'Actions',
    render: (text, record) => {
      return (
        <Popconfirm title="Delete?" onConfirm={() => onDelete(record.key)}>
          <Button danger>Delete</Button>
        </Popconfirm>
      );
    },
  }];
  return (
    <Table
      dataSource={list}
      columns={columns}
    />
  );
};


MyList.propTypes = {
  onDelete: PropTypes.func.isRequired,
  list: PropTypes.array.isRequired,
};

export default MyList;

接下来我们封装一个简单的新增代办组件:通过点击新增按钮弹出一个表单,让用户输入代办事项并确认:

新建pages/TodoList/components/AddNewList.js:

注意:这是一个hook组件,不熟悉的可以去了解一下react hook的用法

import React, { useState } from 'react';

import { Button, Modal, Form, Input } from 'antd';

const CreateForm = ({ visible, onCreate, onCancel}) => {
  const [form] = Form.useForm();
  return (
    <Modal
      visible={visible}
      title="新增代办"
      okText="Create"
      cancelText="Cancel"
      onCancel={onCancel}
      onOk={() => {
        form
          .validateFields()
          .then(values => {
            
            
            form.resetFields();
            onCreate(values);
          })
          .catch(info => {
            console.log('Validate Failed:', info);
          });
      }}
    >
      <Form
        form={form}
        layout="vertical"
        name="form_in_modal"
        initialValues={{
          modifier: 'public',
        }}
      >
        <Form.Item
          name="Title"
          label="Title"
          rules={[
            {
              required: true,
              message: 'Please input the title of collection!',
            },
          ]}
        >
          <Input />
        </Form.Item>
        <Form.Item name="Description" label="Description">
          <Input type="textarea" />
        </Form.Item>
      </Form>
    </Modal>
  );
};
const AddNewList = (props) => {
  const [visible, setVisible] = useState(false);

  const onCreate = (values)=> {
    if(values.Description==undefined){
      values.Description=''
    }
    let NewList =  {
      key: 'a'+Math.random(),
      Title: values.Title,
      Description: values.Description,
    }
    console.log('Received values of form: ', NewList);
   
    setVisible(false);
  };

  return (
    <div>
      <Button
        type="primary"
        onClick={() => {
          setVisible(true);
        }}
        style={{margin:'10px 0px'}}
      >
        新增代办
      </Button>
      <CreateForm
        visible={visible}
        onCreate={onCreate}
        onCancel={() => {
          setVisible(false);
        }}
      />
    </div>
  );
};


export default AddNewList;

页面标题比较简单,我们就在页面上直接编写吧!

(2).编写代办页面

我们在页面上引入我们封装好的UI组件,

新建pages/TodoList/index.js:

import React, { Component } from 'react';
import AddNewList from './components/AddNewList'
import MyList from './components/List'

/**
 * 查询列表
 * 
 */

class index extends Component {

    render() {
        return (
            <div>
            <h2 style={{background:'#1890ff',color:'#fff',height:'85px',lineHeight:'85px',textAlign:'center',borderRadius:'10px',fontSize:'45px'}}>我的代办</h2>
            <AddNewList />
              <MyList onDelete={this.props.handleDelete} list={this.props.list}/>
            </div>
        );
    }
}


export default index;
3.配置菜单

在ant-design pro 中我们需要配置菜单来让菜单生效:

在下添加一行:'menu.todoList':'我的代办'

保存运行我们将会看见:

哦列哦列,我们看下到目前为止的效果吧!

三.将你的页面与Model连接起来

1.mock功能的使用

Mock 数据是前端开发过程中必不可少的一环,在分离前后端开发的情景下是十分必要的,有时候我们后端开发人员开发的接口没有准备好,我们就需要使用mock数据来模拟后台,就不需要等接口都准备好才开始页面功能的编写。umi 里约定 mock 文件夹下的文件即 mock 文件,文件导出接口定义 。我们来看下umi 给的 mock 功能的示例就能了解编写规则。

 export default {
   // 支持值为 Object 和 Array
   'GET /api/users': { users: [1, 2] },
 
   // GET POST 可省略
   '/api/users/1': { id: 1 },
 
   // 支持自定义函数,API 参考 express@4
   'POST /api/users/create': (req, res) => {
     res.end('OK');
   },
 };

其实我们在页面相关目录下用_mock.js这种形式的文件也可定义我们的mock数据,我们直接在文件下定义我们自己的mock,

新建pages/TodoList/_mock.js:

export default {
    'GET /api/todoListApi':[
        {
          key: '1',
          Title: '逛超市',
          Description: '买猫粮',
        },
        {
          key: '2',
          Title: '做家务',
          Description: '把客厅打扫一边',
        },
      ]
}

当本地开发完毕之后,如果服务器的接口满足之前的约定,那么只需要关闭 mock 数据或者代理到服务端的真实接口地址即可。

npm run start:no-mock

2.Services使用

和服务端进行交互时我们需要使用到Services。在 Ant Design Pro 中我们调用统一管理的 service 请求函数, 使用封装的 request.js模块发送请求。这个过程使用了umi-request ,是基于 fetch 封装的开源 http 请求库 ,旨在为开发者提供一个统一的 API 调用方式。

我们新建pages/TodoList/service.js定义一个查询列表的方法:

import request from '@/utils/request';
export async function todoListQuery() {
  return request('/api/todoListApi');
}

3.定义 Model

完成页面开发后,现在我们开始处理数据和逻辑。Model是dvajs里面的概念,dva 通过 model 的概念把一个领域的模型管理起来, 包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。

我们在src/models目录下新建todoList.js文件:

import { todoListQuery } from '../pages/TodoList/service'


const todoListModel = {
    namespace: 'todoList',
    state: {
        list: [],
    },
    reducers: {
        save(state, action) {
            var newState = JSON.parse(JSON.stringify(state))
            newState.list = action.value;
            return newState;
        },
        add(state, action) {
            var newState = JSON.parse(JSON.stringify(state))
            newState.list.push(action.value);
            return newState;
        },
        delete(state,{ payload: key }){
            var newState = JSON.parse(JSON.stringify(state))
            console.log('state--',state,'key:',key)
            newState.list = newState.list.filter(item => item.key !== key);
            return newState;
        }
    },
    effects: {
        *getListApi(_, { call, put }) {
            const response = yield call(todoListQuery);
            console.log('myRes', response)
            yield put({ type: 'save', value: response });
        },
    }
};
export default todoListModel;

概念:Action 与 dispatch 方法

(1)Action:用来描述 UI 层事件的一个对象。

		{
            type: 'todoList/delete',
            payload: key,
        }

(2)dispatch 方法:一个函数方法,用来将 Action 发送给 State。

dispatch({
            type: 'todoList/delete',
            payload: key,
        })

概念:effects 与 reducers

(1)effects:Action 处理器,处理异步动作,基于 Redux-saga 实现。Effect 指的是副作用。我们与后台交互等异步操作就是通过Effect进行的:

 effects: {
         *getListApi(_, { call, put }) {
             const response = yield call(todoListQuery);
             console.log('myRes', response)
             yield put({ type: 'save', value: response });
         }

(2)reducers:Reducer 是 Action 处理器,用来处理同步操作,可以看做是 state 的计算器。它的作用是根据 Action,从上一个 State 算出当前 State。

 reducers: {
         save(state, action) {
             var newState = JSON.parse(JSON.stringify(state))
             newState.list = action.value;
             return newState;
         },
         add(state, action) {
             var newState = JSON.parse(JSON.stringify(state))
             newState.list.push(action.value);
             return newState;
         },
         delete(state,{ payload: key }){
             var newState = JSON.parse(JSON.stringify(state))
             console.log('state--',state,'key:',key)
             newState.list = newState.list.filter(item => item.key !== key);
             return newState;
         }
     }

概念:connect 方法

如果你熟悉 redux,这个 connect 就是 react-redux 的 connect 。connect 是一个函数,绑定 State 到 View。connect 方法返回的也是一个 React 组件,通常称为容器组件。因为它是原始 UI 组件的容器,即在外面包了一层 State。

connect 方法传入的第一个参数是 mapStateToProps 函数,mapStateToProps 函数会返回一个对象,用于建立 State 到 Props 的映射关系。被 connect 的 Component 会自动在 props 中拥有 dispatch 方法。

4.connect 起来

我们改写pages/TodoList/components/AddNewList.js:


import React, { useState } from 'react';
import { connect } from 'react-redux';

import { Button, Modal, Form, Input, Radio } from 'antd';
function mapStateToProps(state) {
  return {

  };
}

function mapDispatchToProps(dispatch) {
  return {
    dispatch
  };
}
const CreateForm = ({ visible, onCreate, onCancel}) => {
  const [form] = Form.useForm();
  return (
    <Modal
      visible={visible}
      title="新增代办"
      okText="Create"
      cancelText="Cancel"
      onCancel={onCancel}
      onOk={() => {
        form
          .validateFields()
          .then(values => {
            
            
            form.resetFields();
            onCreate(values);
          })
          .catch(info => {
            console.log('Validate Failed:', info);
          });
      }}
    >
      <Form
        form={form}
        layout="vertical"
        name="form_in_modal"
        initialValues={{
          modifier: 'public',
        }}
      >
        <Form.Item
          name="Title"
          label="Title"
          rules={[
            {
              required: true,
              message: 'Please input the title of collection!',
            },
          ]}
        >
          <Input />
        </Form.Item>
        <Form.Item name="Description" label="Description">
          <Input type="textarea" />
        </Form.Item>
      </Form>
    </Modal>
  );
};
const AddNewList = (props) => {
  const [visible, setVisible] = useState(false);

  const onCreate = (values)=> {
    if(values.Description==undefined){
      values.Description=''
    }
    let NewList =  {
      key: 'a'+Math.random(),
      Title: values.Title,
      Description: values.Description,
    }
    console.log('Received values of form: ', NewList);
    props.dispatch({
      type:'todoList/add',
      value:NewList
    })
    setVisible(false);
  };

  return (
    <div>
      <Button
        type="primary"
        onClick={() => {
          setVisible(true);
        }}
        style={{margin:'10px 0px'}}
      >
        新增代办
      </Button>
      <CreateForm
        visible={visible}
        onCreate={onCreate}
        onCancel={() => {
          setVisible(false);
        }}
      />
    </div>
  );
};


export default connect(
  mapStateToProps,mapDispatchToProps
)(AddNewList);

改写pages/TodoList/index.js:

import React, { Component } from 'react';
import { connect } from 'dva';
import { message } from 'antd';
import AddNewList from './components/AddNewList'
import MyList from './components/List'

/**
 * 查询列表
 * 
 */

const queryList = async (dispatch) => {
    const hide = message.loading('正在查询');
    try {
      await dispatch({
        type: 'todoList/getListApi',
      });
      hide();
      message.success('查询成功');
      return true;
    } catch (error) {
      hide();
      message.error('查询失败请重试!');
      return false;
    }
  };

class index extends Component {
    componentDidMount(){
      queryList(this.props.dispatch)
    }
    render() {
        return (
            <div>
            <h2 style={{background:'#1890ff',color:'#fff',height:'85px',lineHeight:'85px',textAlign:'center',borderRadius:'10px',fontSize:'45px'}}>我的代办</h2>
            <AddNewList />
              <MyList onDelete={this.props.handleDelete} list={this.props.list}/>
            </div>
        );
    }
}

function mapStateToProps(state) {
    return {
      list: state.todoList.list,
    };
}

function mapDispatchToProps(dispatch) {
    return {
      dispatch,
      handleDelete(key) {
        dispatch({
            type: 'todoList/delete',
            payload: key,
        });
    }
    };
}
export default connect(
    mapStateToProps,mapDispatchToProps
)(index);

刷新浏览器,应该能看到以下效果:

四.结语

至此,我们一个完整的代办记事本模块已经完成了。这个示例包含了完整的流程,让我们对Ant Design Pro中的相关概念都使用了一边,相信各位宝藏前端们已经掌握了吧!哦列哦列,赶紧快快去使用看看吧!

获取源码

公众号后台回复【快速搭建antd-pro2】即可下载

更多热门文章

《Ant Design Pro实战篇:快速搭建属于你的项目》

《react富文本编辑器,这款就够啦!》

《五分钟快速实现炫酷的加载动画》

《小怪鹿react完全开发教程(二) react元素与组件》

《小怪鹿react完全开发教程(一) 进入react的世界》

欢迎关注讨论前端知识:-)不要慌,问题不大!