- https://umijs.org/zh/guide/
- https://umijs.org/zh-CN
- github: umi
- github: create-umi
- umi-plugin-react文档
- umi-plugin-react源码
- umi-plugin-dva
- dva-immer
- immer
- umi-blocks
- pro-blocks
简述
Umi,中文可发音为乌米,是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。
- UmiJS 是一个类 Next.js 的react开发框架。
- 他基于一个约定,即 pages 目录下的文件即路由,而文件则导出 react 组件。
- 然后打通从源码到产物的每个阶段,并配以完善的插件体系,让我们能把 umi 的产物部署到各种场景里。

快速上手
mkdir myapp && cd myapp# 通过官方工具创建项目yarn create @umijs/umi-app # 或 npx @umijs/create-umi-appyarn install # 安装所有依赖yarn start # 启动yarn build # 打包。构建产物默认生成到 ./dist 下,然后通过 “tree ./dist” 命令查看,# 本地验证,类似于使用 http-server ./dist -p 5000yarn global add serverserve ./dist
目录约定
.├── dist/ // 默认的 build 输出目录├── mock/ // mock 文件所在目录,基于 express├── public/ // 此目录下所有文件会被 copy 到输出路径。├── config/├── config.js // umi 配置,同 .umirc.js,二选一└── src/ // 源码目录,可选├── layouts/index.js // 全局布局├── pages/ // 页面目录,里面的文件即路由├── .umi/ // dev 临时目录,需添加到 .gitignore├── .umi-production/ // build 临时目录,会自动删除├── document.ejs // HTML 模板├── 404.js // 404 页面├── page1.js // 页面 1,任意命名,导出 react 组件├── page1.test.js // 用例文件,umi test 会匹配所有 .test.js 和 .e2e.js 结尾的文件└── page2.js // 页面 2,任意命名├── global.css // 约定的全局样式文件,自动引入,也可以用 global.less├── global.js // 可以在这里加入 polyfill├── .umirc.js // umi 配置,同 config/config.js,二选一├── .env // 环境变量└── package.json
命令行工具
- https://umijs.org/zh-CN/docs/cli
```shell"scripts": {"dev": "umi dev", // 本地启动"build": "umi build" // 打包},
查看当前使用的 umi 的版本号,可以使用别名 -v 调用。
$ umi version$ umi -v
查看帮助
$ umi help
$ umi help
查看 webpack 的配置
$ umi webpack $ umi webpack [options]
默认会打印 development 的配置,如需查看 production 配置,需要指定环境变量:
$ NODE_ENV=production umi webpack
快速查看当前项目使用到的所有的 umi 插件
$ umi plugin
当前支持的 type 是 list,可选参数 key。
$ umi plugin list $ umi plugin list —key
<a name="Txonx"></a>## umi build 打包编译构建 web 产物。通常需要针对部署环境,做特定的配置和环境变量修改。相关详情,请查阅[部署](https://umijs.org/zh-CN/docs/deployment)。```shell$ umi build
默认产物输出到项目的 dist 文件夹,你可以通过修改配置 outputPath 指定产物输出目录。 默认编译时会将 public 文件夹内的所有文件,原样拷贝到 dist 目录,如果你不需要这个特性,可以通过配置 chainWebpack 来删除它。
export default {chainWebpack(memo, { env, webpack }) {// 删除 umi 内置插件memo.plugins.delete('copy');}}
注意:如果 public 里面存在产物同名文件,如 index.html,将会导致产物文件被覆盖。
umi dev 启动服务器
启动本地开发服务器进行项目的开发调试
$ umi dev
启动在浏览器中运行的开发服务器,并监视源文件变化,自动热加载。
默认使用 8000 端口,如果 8000 端口被占用,将会使用 8001 端口,以此类推。 你可以通过设置环境变量 PORT 来指定开发端口号。更多环境变量配置,请查阅环境变量。
开启开发服务还会同时提供一个 Network 的链接,你可以在能访问到你当前运行设备的其他设备中预览页面。
注意:如果是在开启了VPN,或者虚拟机等复杂的网络环境中,这个地址很可能会错误。你可以通过访问你真实可用 ip的对应端口号来访问开发页面。
umi generate 生成器
内置的生成器功能,内置的类型有 page ,用于生成最简页面。支持别名调用 umi g。
$ umi generate <type> <name> [options]
这个命令支持扩展,通过 api.registerGenerator 注册,你可以通过插件来实现自己常用的生成器。
import { Generator, IApi } from 'umi';const createPagesGenerator = function ({ api }: { api: IApi }) {return class PageGenerator extends Generator {constructor(opts: any) {super(opts);}async writing() {}};}api.registerGenerator({key: 'pages',Generator: createPageGenerator({ api }),});
umi generate page pageNameumi generate page pageName --typescriptumi generate page pageName --less
更多使用类型和参数,请查阅提供生成器扩展的插件的文档。
环境变量
- https://umijs.org/zh-CN/docs/env-variables
命令行添加
```shellOS X, Linux
$ PORT=3000 umi dev
Windows (cmd.exe)
$ set PORT=3000&&umi dev
如果要同时考虑 OS X 和 Windows,可借助三方工具 cross-env
$ cnpm i -D cross-env $ cross-env PORT=3000 umi dev
<a name="GGYT3"></a>##### 在 .env 文件中定义Umi 中约定根目录下的 .env 为环境变量配置文件。<br />比如:```shellPORT=3000BABEL_CACHE=none
然后执行,
$ umi dev
会以 3000 端口启动 dev server,并且禁用 babel 的缓存。
以下面demo为例-介绍umi基础
初始化
cnpm i -g umimkdir 9.umicd mkdir 9.umicnpm init -y# 新建pages目录mkdir src/pages
约定式路由
除配置式路由外,Umi 也支持约定式路由。约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
如果没有 routes 配置,Umi 会进入约定式路由模式,然后分析 src/pages 目录拿到路由配置。
比如以下文件结构:
.└── pages├── index.tsx└── users.tsx
会得到以下路由配置:
[{ exact: true, path: '/', component: '@/pages/index' },{ exact: true, path: '/users', component: '@/pages/users' },]
注意:满足以下任意规则的文件不会被注册为路由。
- 以 . 或 _ 开头的文件或目录
- 以 d.ts 结尾的类型定义文件
- 以 test.ts、spec.ts、e2e.ts 结尾的测试文件(适用于 .js、.jsx 和 .tsx 文件)
- components 和 component 目录
- utils 和 util 目录
- 不是 .js、.jsx、.ts 或 .tsx 文件
- 文件内容不包含 JSX 元素
创建 首页、用户管理、个人中心3个页面。
首页
❯❯ ~ umi g page index # 通过umi创建一个新的页面,名字叫index(首页)Write: pages\index.jsWrite: pages\index.css❯❯ ~ umi g page user # 创建 user 页面Write: pages\user.jsWrite: pages\user.css❯❯ ~ umi g page profile # 创建 profile 页面Write: pages\profile.jsWrite: pages\profile.css
pages/index.js
import React from 'react';import {Link} from 'umi';export default function Page() {return (<div><h1>首页</h1><Link to="/profile">个人中心</Link></div>);}
pages/profile.js
import React from 'react';import {history} from 'umi';export default function Page() {return (<div><h1>个人中心</h1><button onClick={() => history.goBack()}>返回</button></div>);}
全局 layout
约定 src/layuots/index.js 为全局路由,返回一个Reat组件,通过 props.children 渲染子组件(pages下文件)。
比如以下目录结构:
.└── src├── layouts│ └── index.tsx└── pages├── index.tsx└── users.tsx
会生成路由:
[{exact: false,path: '/',component: '@/layouts/index',routes: [{ exact: true, path: '/', component: '@/pages/index' },{ exact: true, path: '/users', component: '@/pages/users' },],},]
src/layouts/index.js
import React from 'react';import {Link} from 'umi';export default class Layout extends React.Component{render(){return (<div><ul><li><Link to="/">首页</Link></li><li><Link to="/user">用户管理</Link></li><li><Link to="/profile">个人中心</Link></li></ul><div>{this.props.children}</div></div>)}}

嵌套路由
Umi 里约定目录下有 _layout.tsx 时会生成嵌套路由,以 _layout.tsx 为该目录的 layout。layout 文件需要返回一个 React 组件,并通过 props.children 渲染子组件。
比如以下目录结构:
.└── pages└── users├── _layout.tsx├── index.tsx└── list.tsx
会生成路由:
[{exact: false,path: '/users',component: '@/pages/users/_layout',routes: [{ exact: true, path: '/users', component: '@/pages/users/index' },{ exact: true, path: '/users/list', component: '@/pages/users/list' },]}]
src/layouts/index.js 修改
<li><Link to="/user/list">用户管理</Link></li>
src/pages/user/_layout.js (user页面的新入口)
如果有 pages/user/_layout.js, pages/user.js 就自动失效了。
import React from 'react';import {Link} from 'umi';export default class Layout extends React.Component{render(){return (<div><ul><li><Link to="/user/list">用户列表</Link></li><li><Link to="/user/add">新增用户</Link></li></ul><div>{this.props.children}</div></div>)}}
src/pages/user/list.js
import React from 'react';import {Link} from 'umi';export default function List() {return (<ul><li><Link to="/user/detail/1">张三</Link></li><li><Link to="/user/detail/2">李四</Link></li></ul>);}
src/pages/user/add.js
import React from 'react';export default function Add() {return (<form><input type="text" /><button type="submit">提交</button></form>);}
动态路由
约定 [] 包裹的文件或文件夹为动态路由。
比如:
src/pages/users/[id].tsx会成为/users/:idsrc/pages/users/[id]/settings.tsx会成为/users/:id/settings
比如以下文件结构:
.└── pages└── [post]├── index.tsx└── comments.tsx└── users└── [id].tsx└── index.tsx
会生成路由配置:
[{ exact: true, path: '/', component: '@/pages/index' },{ exact: true, path: '/users/:id', component: '@/pages/users/[id]' },{ exact: true, path: '/:post/', component: '@/pages/[post]/index' },{ exact: true, path: '/:post/comments', component: '@/pages/[post]/comments' },];
src/pages/user/detail/[id].js
import React from 'react';import {Link} from 'umi';export default function Detail(props) {console.log(props);return (<div><div>ID: {props.match.params.id}</div></div>);}
动态可选路由
约定 [ $] 包裹的文件或文件夹为动态可选路由。
比如:
src/pages/users/[id$].tsx会成为/users/:id?src/pages/users/[id$]/settings.tsx会成为/users/:id?/settings
比如以下文件结构:
.└── pages└── [post$]└── comments.tsx└── users└── [id$].tsx└── index.tsx
会生成路由配置:
[{ exact: true, path: '/', component: '@/pages/index' },{ exact: true, path: '/users/:id?', component: '@/pages/users/[id$]' },{ exact: true, path: '/:post?/comments', component: '@/pages/[post$]/comments' },];
权限路由
- 通过指定高阶组件
wrappers达成效果。src/pages/profile.js
```jsx import React from ‘react’; import {history} from ‘umi’;
function Profile() { return (
个人中心
// 用 auth 高阶组件包裹 Profile。 Profile.wrappers = [‘@/wrappers/auth’]; export default Profile;
<a name="QD3Ie"></a>##### src/wrappers/auth.js```jsximport {Redirect} from 'umi';function auth(props){const isLogin = localStorage.getItem('isLogin');if (isLogin){return props.children;} else {return <Redirect to={{pathname: '/login', state: {from: '/profile'}}} />}}export default auth;
src/pages/login.js
import React from 'react';import {history} from 'umi';function Login(props){let toLogin = () => {localStorage.setItem('isLogin', 'true');if (props.location.state && props.location.state.from){history.push(props.location.state.from);}}return (<div><h1>登录</h1><button onClick={toLogin}>登录</button></div>)}export default Login;
动态路由
- 运行时配置和配置的区别是他跑在浏览器端,基于此,我们可以在这里写函数、jsx、import 浏览器端依赖等等,注意不要引入 node 依赖。
- 约定
src/app.tsx为运行时配置。
前台运行时
src/components/Foo.js
import React from 'react';export default function Foo(){return <div>Foo</div>}
src/app.js
// 修改路由。// 比如在最前面添加一个 /foo 路由export function patchRoutes({routes}){routes.unshift({path: '/foo',exact: true,component: require('@/components/Foo').default,});}
接口返回
项目目录/mock/routes.js (umi 约定 /mock 文件夹下所有文件为 mock 文件。)
添加后 http://localhost:8001/api/routes 就可以直接访问了,返回 [{path: ‘/foo’, component: ‘Foo.js’}]
export default {// GET 可忽略'GET /api/routes': [{path: '/foo',component: 'Foo.js',}],// 支持自定义函数,API 参考 express@4'POST /api/users/create': (req, res) => {// 添加跨域请求头res.setHeader('Access-Control-Allow-Origin', '*');res.end('ok');},}
src/app.js
let extraRoutes;// 修改 clientRender 参数。export function modifyClientRenderOpts(memo){memo.routes.unshift(...extraRoutes);return memo;}// 覆写 render。export function render(oldRender){fetch('/api/routes').then(res => res.json()).then(res => {extraRoutes = res.map(item => {const component = require(`./components/${item.component}`).default;return {...item, component};})oldRender();})}
