一、Umi简介
一个可插拔的企业级
react应用框架。umi以路由为基础的,以及各种进阶的路由功能,并以此进行功能扩展,比如支持路由级的按需加载。然后配以完善的插件体系,覆盖从源码到构建产物的每个生命周期
1.1 特性
- 开箱即用,内置
react、react-router等 - 支持配置的路由方式
- 完善的插件体系,覆盖从源码到构建产物的每个生命周期
- 高性能,通过插件支持
PWA、以路由为单元的code splitting等 - 支持静态页面导出,适配各种环境,比如中台业务、无线业务、egg、支付宝钱包、云凤蝶等
- 开发启动快,支持一键开启
dll和hard-source-webpack-plugin等 - 一键兼容到
IE9,基于umi-plugin-polyfills - 完善的
TypeScript支持,包括d.ts定义和umi test - 与
dva数据流的深入融合,支持duck directory、model的自动加载、code splitting等等
1.2 架构

1.3 和 dva、roadhog关系
roadhog是基于webpack的封装工具,目的是简化webpack的配置
umi 可以简单地理解为roadhog + 路由,思路类似next.js/nuxt.js,辅以一套插件机制,目的是通过框架的方式简化React开发dva目前是纯粹的数据流,和umi以及roadhog之间并没有相互的依赖关系,可以分开使用也可以一起使用
二、环境搭建
$ mkdir myapp && cd myapp |

确定后,会根据你的选择自动创建好目录和文件

三、目录结构
dva项目之前通常都是这种扁平的组织方式
+ models |
用了
umi后,可以按页面维度进行组织
+ models/global.js |
好处是更加结构更加清晰了,减少耦合,一删全删,方便 copy 和共享
自动注册 models
+ src |
global model为src/models/g.js/a的page model为src/pages/a/models/{a,b,ss/s}.js/c的page model为src/pages/c/model.js/c/d的page model为src/pages/c/model.js,src/pages/c/d/models/d.js
一个复杂应用的目录结构如下
. |
1、dist
默认输出路径,可通过配置
outputPath修改
2、mock
约定
mock目录里所有的.js文件会被解析为mock文件
比如,新建 mock/users.js,内容如下:
export default { |
然后在浏览器里访问
http://localhost:8000/api/users 就可以看到 ['a', 'b']了
3、src
约定 src 为源码目录,但是可选,简单项目可以不加 src 这层目录
比如:下面两种目录结构的效果是一致的。
+ src |
+ pages |
4、src/layouts/index.js
全局布局,实际上是在路由外面套了一层
比如,你的路由是:
[ |
如果有 layouts/index.js,那么路由则变为:
|
5、src/pages
约定
pages下所有的(j|t)sx?文件即路由
6、src/pages/404.js
404页面。注意开发模式下有内置 umi 提供的 404 提示页面,所以只有显式访问/404才能访问到这个页面
7、src/pages/document.ejs
有这个文件时,会覆盖默认的 HTML 模板。需至少包含以下代码,
<div id="root"></div> |
8、src/pages/.umi
这是
umi dev时生产的临时目录,默认包含umi.js和router.js,有些插件也会在这里生成一些其他临时文件。可以在这里做一些验证,但请不要直接在这里修改代码,umi重启或者pages下的文件修改都会重新生成这个文件夹下的文件
9、src/pages/.umi-production
同
src/pagers/.umi,但是是在umi build时生成的,会在umi build执行完自动删除
10、src/global.(j|t)sx?
在入口文件最前面被自动引入,可以考虑在此加入
polyfill
11、src/global.(css|less|sass|scss)
这个文件不走
css modules,自动被引入,可以写一些全局样式,或者做一些样式覆盖
12、.umirc.js 和 config/config.js
umi的配置文件,二选一
13、.env
环境变量,比如:
CLEAR_CONSOLE=none |
四、路由配置
4.1 约定式路由
4.1.1 基础路由
假设 pages 目录结构如下:
+ pages/ |
那么,umi 会自动生成路由配置如下:
[ |
4.1.2 动态路由
umi里约定,带$前缀的目录或文件为动态路由。
比如以下目录结构:
+ pages/ |
会生成路由配置如下:
[ |
4.1.3 可选的动态路由
umi里约定动态路由如果带$后缀,则为可选动态路由。
比如以下结构:
+ pages/ |
会生成路由配置如下:
[ |
4.1.4 嵌套路由
umi里约定目录下有_layout.js时会生成嵌套路由,以_layout.js为该目录的layout。
比如以下目录结构:
+ pages/ |
会生成路由配置如下:
[ |
4.1.5 全局 layout
约定
src/layouts/index.js为全局路由,返回一个 React 组件,通过props.children渲染子组件。
比如:
export default function(props) { |
4.1.6 不同的全局 layout
你可能需要针对不同路由输出不同的全局
layout,umi不支持这样的配置,但你仍可以在layouts/index.js对location.path做区分,渲染不同的layout。
- 比如想要针对
/login输出简单布局,
export default function(props) { |
4.1.7 404 路由
约定
pages/404.js为404页面,需返回React组件。
比如:
export default () => { |
注意:开发模式下,umi 会添加一个默认的
404页面来辅助开发,但你仍然可通过精确地访问/404来验证 404 页面。
4.1.8 通过注释扩展路由
约定路由文件的首个注释如果包含
yaml格式的配置,则会被用于扩展路由。
比如:
+ pages/ |
如果
pages/index.js里包含:
/** |
则会生成路由配置:
[ |
4.2 配置式路由
如果你倾向于使用配置式的路由,可以配置
routes,此配置项存在时则不会对src/pages目录做约定式的解析。
比如:
export default { |
注意:
component是相对于src/pages目录的
4.3 权限路由
umi的权限路由是通过配置路由的Routes属性来实现。约定式的通过yaml注释添加,配置式的直接配上即可。
比如有以下配置:
[ |
然后 umi 会用
./routes/PrivateRoute.js来渲染/list。
./routes/PrivateRoute.js 文件示例:
export default (props) => { |
4.4 路由动效
路由动效应该是有多种实现方式,这里举 react-transition-group 的例子。
先安装依赖,
$ yarn add react-transition-group |
在
layout组件(layouts/index.js或者pages子目录下的 _layout.js)里在渲染子组件时用TransitionGroup和CSSTransition包裹一层,并以location.key为key,
import withRouter from 'umi/withRouter'; |
上面用到的
fade样式,可以在src下的global.css里定义:
.fade-enter { |
4.5 面包屑
面包屑也是有多种实现方式,这里举
react-router-breadcrumbs-hoc的例子。
先安装依赖,
$ yarn add react-router-breadcrumbs-hoc |
然后实现一个 Breakcrumbs.js,比如:
import NavLink from 'umi/navlink'; |
然后在需要的地方引入此 React 组件即可。
4.6 启用 Hash 路由
umi默认是用的Browser History,如果要用Hash History,需配置:
export default { |
4.7 页面间跳转
在 umi 里,页面之间跳转有两种方式:声明式和命令式
声明式
基于
umi/link,通常作为React组件使用。
import Link from 'umi/link'; |
命令式
基于
umi/router,通常在事件处理中被调用。
import router from 'umi/router'; |
五、配置
配置文件
umi允许在.umirc.js或config/config.js(二选一,.umirc.js优先)中进行配置,支持ES6语法。
比如:
export default { |
.umirc.local.js
.umirc.local.js是本地的配置文件,不要提交到git,所以通常需要配置到.gitignore。如果存在,会和.umirc.js合并后再返回。
UMI_ENV
可以通过环境变量
UMI_ENV区分不同环境来指定配置。
举个例子,
// .umirc.js |
不指定 UMI_ENV 时,拿到的配置是:
{ |
指定
UMI_ENV=cloud时,拿到的配置是:
{ |
六、Mock 数据
使用 umi 的 mock 功能
umi里约定mock文件夹下的文件即mock文件,文件导出接口定义,支持基于require动态分析的实时刷新,支持 ES6 语法,以及友好的出错提示
export default { |
当客户端(浏览器)发送请求,如:
GET /api/users,那么本地启动的umi dev会跟此配置文件匹配请求路径以及方法,如果匹配到了,就会将请求通过配置处理
引入 Mock.js
Mock.js是常用的辅助生成模拟数据的第三方库,当然你可以用你喜欢的任意库来结合 roadhog 构建数据模拟功能
import mockjs from 'mockjs'; |
添加跨域请求头
设置
response的请求头即可:
'POST /api/users/create': (req, res) => { |
合理的拆分你的 mock 文件
对于整个系统来说,请求接口是复杂并且繁多的,为了处理大量模拟请求的场景,我们通常把每一个数据模型抽象成一个文件,统一放在 mock 的文件夹中,然后他们会自动被引入

模拟延迟
为了更加真实的模拟网络数据请求,往往需要模拟网络延迟时间
- 手动添加
setTimeout模拟延迟
你可以在重写请求的代理方法,在其中添加模拟延迟的处理,如:
'POST /api/forms': (req, res) => { |
使用插件模拟延迟
上面的方法虽然简便,但是当你需要添加所有的请求延迟的时候,可能就麻烦了,不过可以通过第三方插件来简化这个问题,如:
roadhog-api-doc#delay。
import { delay } from 'roadhog-api-doc'; |
联调
当本地开发完毕之后,如果服务器的接口满足之前的约定,那么你只需要不开本地代理或者重定向代理到目标服务器就可以访问真实的服务端数据,非常方便
七、结合dva实践
自
>= umi@2起,dva的整合可以直接通过umi-plugin-react来配置
特性
- 按目录约定注册
model,无需手动app.model - 文件名即
namespace,可以省去 model 导出的namespace key - 无需手写
router.js,交给umi处理,支持model和component的按需加载 - 内置
query-string处理,无需再手动解码和编码 - 内置
dva-loading和dva-immer,其中dva-immer需通过配置开启 - 开箱即用,无需安装额外依赖,比如
dva、dva-loading、dva-immer、path-to-regexp、object-assign、react、react-dom等`
使用
$ yarn add umi-plugin-react |
然后在 .umirc.js 里配置插件:
export default { |
推荐开启 dva-immer 以简化 reducer 编写,
export default { |
model 注册
model分两类,一是全局model,二是页面model。全局model存于/src/models/目录,所有页面都可引用;页面 model 不能被其他页面所引用。
规则如下:
src/models/**/*.js为global modelsrc/pages/**/models/**/*.js为page modelglobal 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
+ src |
如上目录:
global model为src/models/g.js/a的page model为src/pages/a/models/{a,b,ss/s}.js/c的page model为src/pages/c/model.js`/c/d的page model为src/pages/c/model.js,src/pages/c/d/models/d.js
八、问题汇总
1、如何配置 onError、initialState 等 hook?
新建
src/dva.js,通过导出的config方法来返回额外配置项,比如:
import { message } from 'antd'; |
2、url 变化了,但页面组件也刷新,是什么原因?
layouts/index.js里如果用了connect传数据,需要用umi/withRouter高阶一下
import withRouter from 'umi/withRouter'; |
3、如何访问到 store 或 dispatch 方法?
window.g_app._store |
4、如何禁用包括 component 和 models 的按需加载?
在
.umirc.js里配置:
export default { |
如果不用page.js的命名,倒是能生成路由,但是model、service、components就全部变路由了
不用 page.js,然后通过 umi-plugin-routes 过滤掉不需要的路由,参考 https://github.com/zuiidea/antd-admin/blob/develop/.umirc.js#L4-L16
.umirc.mock.js 这个文件怎么配置呢?
可以不用配置,在 mock/ 下建文件写 mock 代码即可。