天天看点

Umi + AntD Pro 项目搭建

一、Umi 介绍

umi 是可扩展的企业级前端应用框架,Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。

create-react-app 对比:

create-react-app 是基于 webpack 的打包层方案,包含 build、dev、lint 等,他在打包层把体验做到了极致,但是不包含路由,不是框架,也不支持配置。所以,如果大家想基于他修改部分配置,或者希望在打包层之外也做技术收敛时,就会遇到困难。

与nextJs 对比:

next.js 是个很好的选择,Umi 很多功能是参考 next.js 做的。要说有哪些地方不如 Umi,我觉得可能是不够贴近业务,不够接地气。比如 antd、dva 的深度整合,比如国际化、权限、数据流、配置式路由、补丁方案、自动化 external 方面等等一线开发者才会遇到的问题。

官网链接:https://umijs.org/zh-CN/docs

二、项目创建

  1. 创建一个空文件夹作为项目的根目录:/umi
  2. 在目录终端创建项目
    1、yarn create @umijs/umi-app
    2、npx @umijs/create-umi
               
  3. 下载安装包
    1、yarn
    2、npm install
               
  4. 启动项目
    yarn start
               
  5.  项目启动成功界面
    Umi + AntD Pro 项目搭建

二、简单使用

        umi创建后已经帮我们配置好了一些插件,其中最主要的是 @umijs/preset-react,它包含了很多插件              

Umi + AntD Pro 项目搭建

    所以项目可以直接使用antd 组件

import styles from './index.less';
import { Pagination } from 'antd';

export default function IndexPage() {
  return (
    <div>
      <h1 className={styles.title}>Page index</h1>
      <Pagination defaultCurrent={1} total={50} />
    </div>
  );
}
           

   页面效果

Umi + AntD Pro 项目搭建

三、项目结构

 其中 public 与 config 为新建文件,用来存放静态文件及 核心配置

Umi + AntD Pro 项目搭建

四、修改配置目录

Umi + AntD Pro 项目搭建

五、配置路由

  1. 简单路由
    export default [
      { 
        path: '/',
        component: '@/pages/index'
      },
      { 
        path: '/hello',
        component: '@/pages/hello'  // 也可以写作 'hello' 相对路径,会在 src/page 下找
      }
    ]
               
  2. 嵌套路由
    export default [
      { 
        path: '/',
        component: '@/pages/index'
      },
      { 
        path: '/hello',
        component: '@/pages/hello'  // 也可以写作 'hello' 相对路径,会在 src/page 下找
      },
      {
        path: '/user',
        routes: [
          {
            path: '/user/add',
            component: '@/pages/user/add'
          }
        ]
      }
    ]
               
  3. 公共模版

    创建公共页头和页脚 header footer 模块

    在pages 同级创建 components/Header/index.tsx 文件

    import React from 'react';
    
    const Header = (props: any) => {
      return (
        <div>
          <h3>主页/菜单项</h3>
          {props.children}
        </div>
      );
    };
    
    export default Header;
               
    路由部分引入模版组件
    export default [
      { 
        path: '/',
        component: '@/pages/index'
      },
      { 
        path: '/hello',
        component: '@/pages/hello'  // 也可以写作 'hello' 相对路径,会在 src/page 下找
      },
      {
        path: '/user',
        component: '@/components/Header',
        routes: [
          {
            path: '/user/login',
            component: '@/pages/user/login'
          },
          {
            path: '/user/quit',
            component: '@/pages/user/quit'
          }
        ]
      }
    ]
               

六、跳转方法

  1. 声明式跳转 <link to="">  为 umi 自带的声明式跳转路由方法
    import React from 'react';
    import { Link } from 'umi'
    
    const Header = (props: any) => {
      return (
        <div>
          <h3>主页/菜单项</h3>
          <Link to='/user/login'>登录</Link>
          /<Link to='/user/quit'>退出</Link>
          {props.children}
        </div>
      );
    };
    
    export default Header;
               
  2. <NavLink to=""> 专门用于导航栏,点击导航栏时,标签会默认添加 active class类,可以通过此类添加样式
  3. 命令式跳转 (使用 umi 的 history 函数)
    import React from 'react';
    import { NavLink, history} from 'umi'
    import './index.less'
    import { Space, Button } from 'antd'
    
    
    const Header = (props: any) => {
      const gotoLogin = () => {
        history.push('/user/login')
      }
    
      const gotoQuit = () => {
        history.push('/user/quit')
      }
    
      return (
        <div>
          <h3>主页/菜单项</h3>
          {/* <NavLink to='/user/login'>登录</NavLink>
          /<NavLink to='/user/quit'>退出</NavLink> */}
          <Space>
            <Button onClick={gotoLogin}>去登陆</Button>
            <Button onClick={gotoQuit}>退出</Button>
          </Space>
          {props.children}
        </div>
      );
    };
    
    export default Header;
               

七、mock 数据与接口请求

  1. mock 在umi 中是默认启动的,用来模拟api 数据
  2. 添加 mock 文件,只要在 外层 mock 目录中的文件 都会被拆解成 mock 文件  ,新建文件mock/mock.ts
    export default {
      'GET /mock/list': [
        {
          id: 1,
          name: '张三'
        },
        {
          id: 1,
          name: '李四'
        }
      ]
    }
               
    // 同步请求
    const mockTest = () => {
        request('/mock/list').then(res => {
            console.log(res)
        })
    }
    
    // 异步请求
    const mockTest = async () => {
        const res = await request('/mock/list')
        console.log(res)
    }
               
  3. MockJs 工具使用, 可以通过代码随机生成数据

    引入 Mockjs 包

    yarn add mockjs
               
    import mockjs from 'mockjs'
    
    export default {
      'GET /mock/citys': mockjs.mock({
        'list|100': [{name: '@city', 'value|1-100': 50, 'type|0-2': 1}]
      })
    }
               

八、Dva的使用

  1. dva 其实就是个管理数据流的框架,可以更好的在组件之间共用数据,避免了层层 props 传参,也就是dva 是一个数据管理中心
  2. 符合以下规则的被认为是一个model 数据站

    (1)src/models 下的文件

    (2)src/pages 下,子目录中 models 目录下的文件

    (3)src/pages 下,所有的 model.ts 文件(不区分大小写)

  3. Model 的结构

    在src 目录下创建一个 models 文件夹,内部创建一个 msg.ts 文件,结构如下

    export default {
    
      namespace: "dva", // 命名空间,确保唯一
    
      state: {},  // 存放数据
    
      effects: {},  // 接口调用 -- 请求数据
    
      reducers: {}  // 更新状态 -- 修改数据
      
    }
               

    通过指令识别 是否创建成功: umi dva list model

    namespace:当前model的命名空间,同时也是全局 state 上的一个属性

    state:数据初始值

    effects:effect 要点如下(难点)

    (1)处理异步操作

    (2)需要通过 put 调用 reducers 更新 state

    (3)只能是 generator 函数,有 action 和 effects 两个参数。其中 effects 包含put、 call、 sellect 三个字段,put 用于触发 action, call 用于异步处理逻辑

  4. dva  连接,  在 pages 下创建一个 msg.tsx 文件
    import React from 'react';
    import {connect} from 'umi'
    
    const Msg = () => {
      return (
        <div>
          <h1>Dva 测试</h1>
        </div>
      )
    }
    
    export default connect(({msg}) => ({msg}))(Msg)
               
    connect 的作用是 将数据绑定到组件上
  5. dva  数据使用步骤

    (1)src 目录下创建 services/api.ts 文件

    import { request } from 'umi'
    
    export async function getCitys() {
      return request('/mock/citys')
    }
               
    (2)  models/msg.ts  dva 文件调用上面接口保存数据
    import { getCitys } from '@/services/api'
    
    export default {
    
      namespace: "msg", // 命名空间,确保唯一
    
      state: {
        cityList: [],
      },  // 存放数据
    
      effects: {  // 接口调用 -- 请求数据
        //payload 获取组件中参数,例如表单提交获得到的数据
        //callback 回调函数,例如提交成功后跳出一个提示框
        //put 触发action
        //call 访问外部方法,获取外部数据 call(方法,方法传参)
        * queryCityList({payload, callback}, {put, call}) {
          //获取tags数据,yield实现异步
          const res = yield call(getCitys)
          //调用reducers,更新state
          yield put({
            type: 'setCityList', // 类似于 redux 中 action 的 type
            payload: res
          })
        }
      },  
      // 是一个纯函数,用于处理同步操作,是唯一可以修改 state 的地方,由 action 触发,有 state 和 action 两个参数
      reducers: {  // 更新状态 -- 修改数据
        setCityList(state, action) {
          return {...state, cityList: action.payload}
        }
      }
    }
               
    (3)页面内部使用dva
    import React from 'react';
    import {connect} from 'umi'
    import { Button } from 'antd'
    
    const Msg = (props: any) => {
      const {dispatch} = props;
    
      const list = props.msg.cityList.list || []
    
      const getData = () => {
        // 使用 model 获取数据
        dispatch({
          type: 'msg/queryCityList',
          payload: null
        })
      }
    
      return (
        <div>
          <h1>Dva 测试</h1>
          <Button onClick={getData}>获取列表数据</Button>
          <table>
            <tr><th>城市名</th><th>数量</th><th>城市类型</th></tr>
            {
              list.map((item, index) => {
                return <tr key={index}>
                  <td>{item.name}</td>
                  <td>{item.value}</td>
                  <td>{item.type}</td>
                </tr>
              })
            }
          </table>
        </div>
      )
    }
    
    export default connect(({msg}) => ({msg}))(Msg)
               
    因为 export default connect(({msg})=>({msg}))(Msg); 把数据都传到了Msg里面的props.msg, 通过其中的 dispatch 发送action获取我们的数据,通过 type: 'msg/queryCityList' 调用 effect 中的 generator 函数

继续阅读