简介
-
Umi
,中文可发音为乌米,是可扩展的企业级前端应用框架 -
该框架集合了
react
、dva
、antd
等 -
支持
Typescript
-
支持类似于
next.js
的约定式路由,也支持配置式路由
常用命令
npm install umi -g
:npm
全局安装umi
yarn global add umi
:yarn
全局安装umi
umi g page index
:创建index
首页,会创建一个pages/
文件夹,里面有index.js
和index.css
umi g page users/index
:会创建一个pages/users/
文件夹,里面有index.js
和index.css
项目搭建
-
全局安装
yarn
:npm install yarn -g
-
使用
yarn
安装umi
:yarn global add umi
-
cd
到项目目录下 -
构建
umi
项目:yarn create umi
-
(或者)构建
umi
项目:yarn create @umijs/umi-app
-
安装依赖:
yarn
-
启动项目:
yarn start
目录结构
├── config/
├── config.js // umi 配置,同 .umirc.js,二选一
├── dist/ // 默认的 build 输出目录
├── mock/ // mock 文件所在目录,基于 express
├── public/ // 全局相对路径文件
└── src/ // 源码目录
├── assets/ // 静态文件
├── components/ // 全局共用组件
├── layouts/index.js // 全局布局文件
├── models/ // 全局models文件,存放全局共用数据store
├── pages/ // 页面目录,业务组件
├── .umi/ // dev 临时目录,需添加到 .gitignore
├── .umi-production/ // build 临时目录,会自动删除
├── index/ // 首页模块
├── manager/ // 管理端模块
├── components/ // 管理端-局部公共组件
├── models/ // 管理端-局部models,存放manager的store
├── services/ // 管理端-局部services,存放manager的接口
├── index.js // 业务组件index
├── page.js // 业务组件page
├── _layout.js // 局部入口文件
├── 404.js // 404 页面
├── document.ejs // 如果有这个文件,会覆盖默认的HTML模板,至少需要有 <div id="root"></div>
├── services/ // 全局services文件,存放全局公共接口
├── utils/ // 全局工具类
├── global.css // 约定的全局样式文件,自动引入,也可以用 global.less
├── global.js // 约定的全局Js文件,自动引入,可以在这里加入 polyfill
├── app.js // 运行时配置文件
├── .umirc.js // umi 配置,同 config/config.js,二选一
├── .env // 环境变量
└── package.json
umi
配置参考:UmiJS常用配置
路由
约定式路由
-
umi
约定,在pages
目录下的.js/.jsx
会自动生成路由,除开在配置文件plugins/routes
中被exclude
的目录或文件,文件的路径即路由 -
src/layout/
为全局layout
,默认全局入口文件,配置式路由下无效。
嵌套路由
pages/
下任何文件下的_layout.js
即当前文件夹路由下的入口文件,必须先经过_layout.js
才能进入当前文件夹路由_layout.js
中必须有props.children
。通过props.children
渲染子组件
嵌套路由示例:/users
-
在
src/pages/
目录下创建users/
目录 -
在
src/page/users/
目录下创建_layout.jsx
文件
// props.children 引入子组件,如果没有创建子组件,则会报错
export default function(props) {
return (
<div>
<h1>Page users _layout</h1>
<div>{props.children}</div>
</div>
)
}
-
在
src/page/users/
目录下创建index.jsx
文件 -
访问嵌套路由:
http://localhost:8000/users
动态路由
-
文件的命名以
$
为开头 -
users/$id.js
对应路由为/users/:id
。页面组件中用props.match.params.id
获取路由参数 -
如果带
$
后缀,则为可选动态路由。比如:users/$id$.js
注释扩展路由
约定路由文件的首个注释如果包含 yaml 格式的配置,则会被用于扩展路由。
/**
* title: Index Page
* Routes:
* - ./src/routes/a.js
* - ./src/routes/b.js
*/
对应生成的路由配置
[
{ path: '/', component: './index.js',
title: 'Index Page',
Routes: [ './src/routes/a.js', './src/routes/b.js' ],
},
]
配置式路由
-
在配置文件
.umirc.(ts|js)
或者config/config.(ts|js)
中配置 -
此配置项存在时则不会对
src/pages
目录做约定式的解析 -
component
是相对于src/pages
目录的
// 路由示例
export default {
routes: [
{
path: '/',
component: '../layouts/index',
routes: [
{ path: '/user', redirect: '/user/login' },//redirect,避免只渲染_layout.js
{ path: '/user/login', component: './user/login' },
{
path: '/manager', component: '../pages/management/_layout.js',
routes: [
{ path: '/manager/system', component: '../pages/management/manager/system', Routes: ['./routes/PrivateRoute.js'] }
}
],
},
],
};
权限路由
-
路由通过
Routes
属性来实现权限路由(路由守卫) -
约定式的通过
yaml
注释添加,配置式的直接配上即可
比如如下路由配置
[
{ path: '/', component: './pages/index.js' },
{ path: '/list', component: './pages/list.js', Routes: ['./routes/PrivateRoute.js'] },
]
umi
会用./routes/PrivateRoute.js
来渲染/list
// ./routes/PrivateRoute.js
import Redirect from 'umi/redirect'
export default (props) => {
if(Math.random()>0.5) {
return <Redirect to="/login" /> //没有登录时,重定向到登录页
}
// 登录成功时,显示子路由的页面组件
return (
<div>
{ props.children }
</div>
);
}
跳转路由
Link
方式
import Link from 'umi/link';
<link to="/list">Go to list page</Link>
router
方式
import router from 'umi/router';
router.push({
pathname: '/list',
query: {
a: b
}
})
withRouter
-
未经路由跳转的组件,如子组件想拿到路由的相关信息
location
、history
、match
等时可用,经路由跳转的页面则默认已有路由信息 -
比如:
layouts/index.js
里如果用了connect
传数据,需要用umi/withRouter
高阶一下
import withRouter from 'umi/withRouter';
export default withRouter(connect()(Layout));
路由动效
使用插件react-transition-group
- 修改
layout
组件,渲染子组件时用TransitionGroup
和CSSTransition
包裹一层,并以location.key
为key
import withRouter from "umi/withRouter";
import { TransitionGroup, CSSTransition } from "react-transition-group";
export default withRouter(
({ location }) =>
<TransitionGroup>
<CSSTransition key={location.pathname} classNames="fade" timeout={300}>
{ children }
</CSSTransition>
</TransitionGroup>
)
fade
样式,可以在src/global.css
文件中定义
.fade-enter {
opacity: 0;
z-index: 1;
}
.fade-enter.fade-enter-active {
opacity: 1;
transition: opacity 250ms ease-in;
}
使用dva
dva
主要是软件分层的概念components
负责与用户直接交互:渲染页面、接收用户的操作输入,侧重于展示型和交互逻辑models
负责处理业务逻辑,可以理解成一个维护页面数据状态的对象,为展示层做数据、状态的读写等操作services
主要负责与HTTP
做接口对接,跟后端做数据交互,读写数据
dva
的数据流向
view
层dispatch
操作action
- 触发
model
层effect
中相应方法- 触发
call
发起services
层请求,获取接口数据- 触发
put
发起reducer
处理相应的action
更新数据- 更新
model
层中state
- 触发
view
层的render
方法进行重新渲染- 页面更新
dva
已经融合进了umi
,在config/config.js
中打开dva
的开关
plugins: [
[ "umi-plugin-react", { antd: true, dva: true } ]
]
-
umi/dva
是基于redux
,redux-saga
的数据流方案 -
umi
会按照约定的目录来自动注册model
,且文件名会被识别为model
的namespace
model
分为全局model
和页面model
src/models/**/*.js
为global model
src/pages/**/models/**/*.js
为page model
global model
全量载入,page model
在production
时按需载入,在development
时全量载入page model
为page js
所在路径下models/**/*.js
的文件page model
会向上查找,比如page js
为pages/a/b.js
,他的page model
为pages/a/b/models/**/*.js + pages/a/models/**/*.js
,依次类推- 约定
model.js
为单文件model
,解决只有一个model
时不需要建models
目录的问题,有model.js
则不去找models/**/*.js
dva
的connect
即redux
的connect
,dva
的model
即redux-saga
简化版,更易用
import React from 'react';
import { connect } from 'dva';
import ProductList from '@/components/products/list/index'
@connect(
({ products }) => ({ products })
)
class Products extends Component {
componentDidMount() {
console.log(this.props.products)
}
render() {
return (
<div>
<h2>List of Products</h2>
</div>
);
}
}
export default Products
dva
使用可参考:
mock
- 约定
mock/
目录里所有的.js
文件会被解析为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
import mockjs from 'mockjs'
export default {
// 使用 mockjs 等三方库
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
}),
}
添加跨域请求头
export default {
'POST /api/users/create': (req, res) => {
// ...
res.setHeader('Access-Control-Allow-Origin', '*');
// ...
},
}
模拟延迟
- 手动添加
setTimeout
模拟延迟
export default {
'POST /api/forms': (req, res) => {
setTimeout(() => {
res.send('Ok');
}, 1000);
},
}
- 使用
roadhog-api-doc#delay
统一处理
import { delay } from 'roadhog-api-doc';
const proxy = {
'GET /api/project/notice': getNotice,
'GET /api/activities': getActivities,
'GET /api/rule': getRule,
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }]
}),
'GET /api/fake_list': getFakeList,
'GET /api/fake_chart_data': getFakeChartData,
'GET /api/profile/basic': getProfileBasicData,
'GET /api/profile/advanced': getProfileAdvancedData,
'POST /api/register': (req, res) => {
res.send({ status: 'ok' });
},
'GET /api/notices': getNotices,
};
// 调用 delay 函数,统一处理
export default delay(proxy, 1000);
发表评论