在编写React项目的时候,我们常常需要安装很多的包,并对这些包进行配置后才可以使用,每次都需要配置对于我们来说真的太麻烦了。那么我们需要怎么简化这个操作呢?下面为大家带来Umi(乌米)本章内容主要介绍Umi2.x的版本(最新版本3.x)
官网地址
Umi官网:跳转到Umi官网
Umi,中文可发音为乌米,是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。
Umi 是蚂蚁金服的底层前端框架,已直接或间接地服务了 3000+ 应用,包括 java、node、H5 无线、离线(Hybrid)应用、纯前端 assets 应用、CMS 应用等。他已经很好地服务了我们的内部用户,同时希望他也能服务好外部用户。
上面的内容引自Umi官方文档。综上所述Umi可以说对我们开发人员真的是很友好呢!!
- 1、需要支持 IE 8 或更低版本的浏览器
- 2、需要支持 React 16.8.0 以下的 React
- 3、需要跑在 Node 10 以下的环境中
- 4、有很强的 webpack 自定义需求和主观意愿
- 5、需要选择不同的路由方案
如果你满足上面的条件,那么Umi不适合你。有些小伙伴会问为什么不使用create-react-app、next.js来书写React项目呢?
create-react-app 是基于 webpack 的打包层方案,包含 build、dev、lint 等,他在打包层把体验做到了极致,但是不包含路由,不是框架,也不支持配置。所以,如果大家想基于他修改部分配置,或者希望在打包层之外也做技术收敛时,就会遇到困难。
next.js 是个很好的选择,Umi 很多功能是参考 next.js 做的。要说有哪些地方不如 Umi,我觉得可能是不够贴近业务,不够接地气。比如 antd、dva 的深度整合,比如国际化、权限、数据流、配置式路由、补丁方案、自动化 external 方面等等一线开发者才会遇到的问题。
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
总的来说就是收敛React项目经常使用的技术和插件,并把这些内容进行封装。
Umi如何工作?
首先得有 node,并确保 node 版本是 10.13 或以上。(mac 下推荐使用 nvm 来管理 node 版本)
node -v
查看当前Node版本。
推荐使用 yarn 管理 npm 依赖,并使用国内源(阿里用户使用内网源)。
# 国内源
$ npm i yarn tyarn -g
# 后面文档里的 yarn 换成 tyarn
$ tyarn -v
如果对于环境还没配置好的同志可以看这篇node环境搭建(node安装、npm/cnpm & yarn/tyarn 缓存设置 与 镜像配置 )
1、先找个地方建个空目录。
$ mkdir myapp && cd myapp
2、通过官方工具创建项目
$ yarn create umi
安装的时候会出现配置选项,回车即下一步,空格即选中。
在你创建myapp目录中打开终端输入。
$ yarn
依赖安装完成后就可以启动项目了。
$ yarn start
$ yarn build
打包好的内容会存储在更路径下的dist文件夹中。
├── dist/ // 默认的 build 输出目录
├── mock/ // mock 文件所在目录,基于 express
├── 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
什么是约定式路由,就是Umi约定好的路由。不需要自己配置即可使用。这里会梳理约定是的所有路由大纲,对于如何实现请点击这里Umi约定式路由。
如果你倾向于使用配置式的路由,可以配置 .umirc.(ts|js)
或者 config/config.(ts|js)
配置文件中的 routes
属性,此配置项存在时则不会对 src/pages
目录做约定式的解析。
export default {
routes: [
{ path: '/', component: './a' },
{ path: '/list', component: './b', Routes: ['./routes/PrivateRoute.js'] },
{ path: '/users', component: './users/_layout',
routes: [
{ path: '/users/detail', component: './users/detail' },
{ path: '/users/:id', component: './users/id' }
]
},
],
};
umi 的权限路由是通过配置路由的 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
文件示例:
export default (props) => {
return (
<div>
<div>PrivateRoute (routes/PrivateRoute.js)</div>
{ props.children }
</div>
);
}
umi 允许在 .umirc.js
或 config/config.js
(二选一,.umirc.js
优先)中进行配置,支持 ES6 语法。
在根目录中创建config文件目录,并创建config.js文件。我们公司平时开发都是卸载config.js文件中。
这个基本上又是webpack的配置了。
类型:Array
默认值:[]
数组项为指向插件的路径,可以是 npm 依赖、相对路径或绝对路径。如果是相对路径,则会从项目根目录开始找。比如:
export default {
plugins: [
// npm 依赖
'umi-plugin-react',{
antd: true,
dva: true,
dynamicImport: false,
title: 'Umi_client',
dll: false,
}
],
};
Array
null
umi 的路由基于 react-router 实现,配置和 react-router@4 基本一致,详见路由配置章节。
export default {
routes: [
{
path: '/',
component: '../layouts/index',
routes: [
{ path: '/user', redirect: '/user/login' },
{ path: '/user/login', component: './user/login' },
],
},
],
};
注:
src/pages
目录开始解析的routes
,则优先使用配置式路由,且约定式路由会不生效Boolean
false
禁用 redirect 上提。
出于一些原因的考虑,我们在处理路由时把所有 redirect 声明提到路由最前面进行匹配,但这导致了一些问题,所以添加了这个配置项,禁用 redirect 上提。
export default {
disableRedirectHoist: true,
};
String | [String, Object]
browser
指定 history 类型,可选 browser
、hash
和 memory
。
比如:
export default {
history: 'hash',
};
注:
1、Umi默认路由是‘browser’
String
./dist
指定输出路径。
不允许设置
src
、public
、pages
、mock
、config
等约定目录
String
/
指定 react-router 的 base,部署到非根目录时需要配置
String
/
指定 webpack 的 publicPath,指向静态资源文件所在的路径。
Boolean
false
是否开启 hash 文件后缀。
Object
{ chrome: 49, firefox: 45, safari: 10, edge: 13, ios: 10 }
配置浏览器最低版本,会自动引入 polyfill 和做语法转换,配置的 targets 会和合并到默认值,所以不需要重复配置。
比如要兼容 ie11,需配置:
export default {
targets: {
ie: 11,
},
};
Object
{}
配置全局 context,会覆盖到每个 pages 里的 context。
Array
of String
[]
排除 mock 目录下不作 mock 处理的文件。
比如要 exclude 所有 _
前缀的文件和文件夹,
export default {
mock: {
exclude: ['mock/**/_*.js', 'mock/_*/**/*.js'],
},
};
Object
{ defaultGitUrl: "https://github.com/umijs/umi-blocks" }
export default {
block: {
defaultGitUrl: 'https://github.com/ant-design/pro-blocks',
npmClient: 'cnpm', // 优先级低于 umi block add [block] --npm-client
},
};
Boolean | Object
false
用于服务端渲染(Server-Side Render)。
开启后,生成客户端静态文件的同时,也会生成 umi.server.js
和 ssr-client-mainifest.json
文件。
export default {
ssr: {
// https://github.com/liady/webpack-node-externals#optionswhitelist-
externalWhitelist?: [];
// webpack-node-externals 配置,排除 whiteList
nodeExternalsOpts?: {};
// 客户端资源 manifest 文件名,默认是 ssr-client-mainifest.json
manifestFileName: 'ssr-client-mainifest.json',
// 关闭 ssr external,全量打入 umi.server.js
disableExternal: false,
// 关闭 ssr external 时,白名单模块将进入 externa
// 可用于 react-helmet, react-document-title
disableExternalWhiteList?: string[] | object;
},
};
其中 ssr-client-mainifest.json
是按路由级别的资源映射文件,例如:
{
"/": {
"js": [
"umi.6791e2ab.js",
"vendors.aed9ac63.async.js",
"layouts__index.12df59f1.async.js",
"p__index.c2bcd95d.async.js"
],
"css": [
"umi.baa67d11.css",
"vendors.431f0bf4.chunk.css",
"layouts__index.0ab34177.chunk.css",
"p__index.1353f910.chunk.css"
]
},
"/news/:id": {
"js": [
"umi.6791e2ab.js",
"vendors.aed9ac63.async.js",
"layouts__index.12df59f1.async.js",
"p__news__$id.204a3fac.async.js"
],
"css": ["umi.baa67d11.css", "vendors.431f0bf4.chunk.css", "layouts__index.0ab34177.chunk.css"]
}
}
在 Node.js 中使用如下:
/**
*
* @param {*}
* ctx(server 执行上下文,`serverRender` 通过 `ctx.req.url` 获取当前路由)
* @return html 片段
*/
async function UmiServerRender(ctx) {
// mock 一个 window 对象
global.window = {};
// 引入模块
const serverRender = require('./dist/umi.server');
// 提供 react-dom/server,避免 React hooks ssr 报错
const { ReactDOMServer } = serverRender;
const {
// 当前路由元素
rootContainer,
// 页面模板
htmlElement,
// 匹配成功的前端路由,比如 /user/:id
matchPath,
// 初始化 store 数据,若使用 dva
g_initialData,
} = await serverRender.default(ctx);
// 元素渲染成 html
const ssrHtml = ReactDOMServer.renderToString(htmlElement);
return ssrHtml;
}
页面进行数据预取:
// pages/news/$id.jsx
const News = props => {
const { id, name, count } = props || {};
return (
<div>
<p>
{id}-{name}
</p>
</div>
);
};
/**
*
* @param {*}
* {
* route (当前路由信息)
* location (history 对象有 location, query, ...)
* store(需开启 `dva: true`,`store.dispatch()` 会返回 Promise)
* isServer (是否为服务端执行环境)
* req (HTTP Request 对象,只存在于 Server 端)
* res (HTTP Response 对象,只存在于 Server 端)
* }
*/
News.getInitialProps = async ({ route, location, store, isServer, req, res }) => {
const { id } = route.params;
// ?locale=en-US => query: { locale: 'en-US' }
const { query } = location;
const data = [
{
id: 0,
name: 'zero',
},
{
id: 1,
name: 'hello',
},
{
id: 2,
name: 'world',
},
];
return Promise.resolve(data[id] || data[0]);
};
数据预取可将之前使用
componentDidMount
或React.useEffect
时机获取数据的方法,移至getInitialProps
。
预渲染(Pre-Rendering)使用,umi-example-ssr-with-egg
以下内容来源于官网,我只是提取了常用的来显示,如果有需求请自己去官网查看。
通过 webpack-chain 的 API 扩展或修改 webpack 配置。
比如:
chainWebpack(config, { webpack }) {
// 设置 alias
config.resolve.alias.set('a', 'path/to/a');
// 删除进度条插件
config.plugins.delete('progress');
}
配置主题,实际上是配 less 变量。支持对象和字符串两种类型,字符串需要指向一个返回配置的文件。比如:
"theme": {
"@primary-color": "#1DA57A"
}
或者,
"theme": "./theme-config.js"
Boolean
false
配置是否开启 treeShaking,默认关闭。
e.g.
export default {
treeShaking: true,
};
比如 ant-design-pro 开启 tree-shaking 之后,gzip 后的尺寸能减少 10K
通过 webpack 的 DefinePlugin 传递给代码,值会自动做 JSON.stringify
处理。比如:
"define": {
"process.env.TEST": 1,
"USE_COMMA": 2,
}
配置 webpack 的?externals?属性。比如:
// 配置 react 和 react-dom 不打入代码
"externals": {
"react": "window.React",
"react-dom": "window.ReactDOM"
}
配置 webpack-dev-server 的 proxy 属性。如果要代理请求到其他服务器,可以这样配:
"proxy": {
"/api": {
"target": "http://jsonplaceholder.typicode.com/",
"changeOrigin": true,
"pathRewrite": { "^/api" : "" }
}
}
然后访问 /api/users
就能访问到 http://jsonplaceholder.typicode.com/users 的数据
配置 node-sass 的选项。注意:使用 sass 时需在项目目录安装 node-sass 和 sass-loader 依赖
配置后会生成 asset-manifest.json,option 传给 https://www.npmjs.com/package/webpack-manifest-plugin。比如:
"manifest": {
"basePath": "/app/"
}
新建 src/pages/document.ejs
,umi 约定如果这个文件存在,会作为默认模板,内容上需要保证有 ,比如:
<html>
<head>
<meta charset="utf-8" />
<title>Your Apptitle>
head>
<body>
<div id="root">div>
body>
html>
模板里可通过 context
来获取到 umi 提供的变量,context 包含:
route
,路由对象,包含 path、component 等config
,用户配置信息publicPath
2.1.2+,webpack 的 output.publicPath
配置env
,环境变量,值为 development 或 production模板基于 ejs 渲染,可以参考 https://github.com/mde/ejs 查看具体使用。
比如输出变量,
<link rel="icon" type="image/x-icon" href="<%= context.publicPath %>favicon.png" />
比如条件判断,
<% if(context.env === 'production') { %>
<h2>生产环境h2>
<% } else {%>
<h2>开发环境h2>
<% } %>
注意
此功能需开启 exportStatic
配置,否则只会输出一个 html 文件。
提示
优先级是:路由的 document 属性 > src/pages/document.ejs > umi 内置模板
配置路由的 document 属性。
比如约定式路由可通过注释扩展 document
属性,路径从项目根目录开始找,
/**
* document: ./src/documents/404.ejs
*/
然后这个路由就会以 ./src/documents/404.ejs
为模板输出 HTML。
这是官方封装的一个插件集,包含 18 个常用的进阶功能。
$ yarn add umi-plugin-react --dev
在 .umirc.js
里配置:
export default {
plugins: [
[
'umi-plugin-react',
{
dva: {
immer: true,
},
antd: true,
routes: {
exclude: [/models\//],
},
polyfills: ['ie9'],
locale: {},
library: 'react',
dynamicImport: {
webpackChunkName: true,
loadingComponent: './components/Loading.js',
},
dll: {
exclude: [],
},
pwa: true,
hd: true,
fastClick: true,
title: 'default title',
chunks: ['vendor', 'umi'],
scripts: [
{ src: 'http://cdn/a.js' },
{ src: '<%= PUBLIC_PATH %>a.js' },
{ content: `alert('a');` },
],
headScripts: [],
metas: [{ charset: 'utf-8' }],
links: [{ rel: 'stylesheet', href: 'http://cdn/a.css' }],
},
],
],
};
所有功能默认关闭,有真值配置才会开启。
Object
基于 umi-plugin-dva 实现,功能详见 和 dva 一起用。
配置项包含:
immer
,是否启用 dva-immerdynamicImport
,是否启用按需加载,配置项同 #dynamicImport,并且如果在 #dynamicImport 有配置,配置项会继承到 dva 中hmr
,是否启用 dva 的 hmr注意
如果项目中有 dva 依赖,则优先使用项目中的依赖。
Boolean
启用后自动配置 babel-plugin-import 实现 antd, antd-mobile 和 antd-pro 的按需编译,并且内置 antd, antd-mobile 依赖,无需手动在项目中安装。
注意
如果项目中有 antd 或者 antd-mobile 依赖,则优先使用项目中的依赖。
Object
基于 umi-plugin-routes 实现,用于批量修改路由。
配置项包含:
exclude
,值为 Array(RegExp)
,用于忽略某些路由,比如使用 dva 后,通常需要忽略 models、components、services 等目录update
, 值为 Function
,用于更新路由Object
基于 umi-plugin-locale 和 react-intl 实现,用于解决 i18n 问题。
配置项包含:
default: 'zh-CN'
, // default zh-CN, if baseSeparator set _
,default zh_CNbaseNavigator: true
, // default true, when it is true, will use navigator.language
overwrite defaultantd: true
, // use antd, default is truebaseSeparator: '-'
, // the separator between lang
and language
, default -
Object
实现路由级的动态加载(code splitting),可按需指定哪一级的按需加载。
配置项包含:
webpackChunkName
,是否通过 webpackChunkName 实现有意义的异步文件名loadingComponent
,指定加载时的组件路径level
,指定按需加载的路由等级Object
通过 webpack 的 dll 插件预打包一份 dll 文件来达到二次启动提速的目的。
配置项包含:
include
exclude
预渲染插件
$ yarn add @umijs/plugin-prerender --dev
在 .umirc.js
里配置:
export default {
ssr: true,
plugins: [['@umijs/plugin-prerender']],
};
所有功能默认关闭,有真值配置才会开启。
exclude
string[]
排除不需要预渲染的页面. 例如:[ '/user', '/about', '/news/:id' ]
Boolean
在服务端环境模拟 window
变量
项目代码最好兼容下服务端渲染,在服务端渲染的生命周期中,需要加上 typeof bar !== undefined
判断,其中 bar
是浏览器端变量或方法。 :::
Boolean
预渲染出来的 html 片段是否可见,主要用于避免动态数据下的页面闪烁。
通过声明的方式做路由跳转。
例子:
import Link from 'umi/link';
export default () => {
<div>
/* 普通使用 */
<Link to="/list">Go to list pageLink>
/* 带参数 */
<Link to="/list?a=b">Go to list pageLink>
/* 包含子组件 */
<Link to="/list?a=b"><button>Go to list pagebutton>Link>
div>
}
通过编程的方式做路由切换,包含以下 4 个 API 。
推一个新的页面到 history 里。
例子:
import router from 'umi/router';
// 普通跳转,不带参数
router.push('/list');
// 带参数
router.push('/list?a=b');
router.push({
pathname: '/list',
query: {
a: 'b',
},
});
# 对象且不包含 pathname 会报错
router.push({
query: {}
});
替换当前页面,参数和 router.push() 相同。
往前或往后跳指定页数。
例子:
import router from 'umi/router';
router.go(-1);
router.go(2);
后退一页。
例子:
import router from 'umi/router';
router.goBack();
重定向用。
例子:
import Redirect from 'umi/redirect';
<Redirect to="/login" />;
详见:https://reacttraining.com/react-router/web/api/Redirect
在根路径下创建mock文件夹,创建一个mock.js文件,内容如下。
export default {
// 支持值为 Object 和 Array
'GET /api/users': { users: [1, 2] },
// GET 可忽略
'/api/users/1': { id: 1 },
// 支持自定义函数,API 参考 express@4
'POST /api/users/create': (req, res) => {
// 添加跨域请求头
res.setHeader('Access-Control-Allow-Origin', '*');
res.end('ok');
},
}
Mock.js 是常用的辅助生成模拟数据的三方库,借助他可以提升我们的 mock 数据能力。
比如:
import mockjs from 'mockjs';
export default {
// 使用 mockjs 等三方库
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
}),
};
设置response的请求头即可:
'POST /api/users/create':(req,res) => {
res.setHeader('Access-Control-Allow-Origin','*')
}
对于整个系统来说,请求接口是复杂并且繁多的,为了处理大量模拟请求的场景,我们通常把每一个数据模型抽象成一个文件,统一放在 mock
的文件夹中,然后他们会自动被引入。
为了更加真实的模拟网络数据请求,往往需要模拟网络延迟时间。
你可以在重写请求的代理方法,在其中添加模拟延迟的处理,如:
'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);
如果你需要动态生成 Mock 数据,你应该通过函数进行处理,
比如:
// 静态的
'/api/random': Mock.mock({
// 只随机一次
'number|1-100': 100,
}),
// 动态的
'/api/random': (req, res) => {
res.send(Mock.mock({
// 每次请求均产生随机值
'number|1-100': 100,
}))
},
当本地开发完毕之后,如果服务器的接口满足之前的约定,那么你只需要不开本地代理或者重定向代理到目标服务器就可以访问真实的服务端数据,非常方便。
在配置文件开启 ssr: true
,更多配置:
export default {
ssr: true,
};
开启后,运行 umi build
,会生成如下文件:
.
├── dist
│ ├── index.html
│ ├── ssr-client-mainifest.json
│ ├── umi.css
│ ├── umi.js
│ └── umi.server.js
由于与服务端框架无关,Umi 关注是应用 UI 层渲染,与服务端框架不耦合。
为了降低服务端框架接入门槛,Umi 提供 umi-server,并以常见的 Node.js 服务端框架(Koajs、Express、Egg.js)为例,给出具体接入方式。
用 Node.js 原生 http 模块做服务端渲染。
// bar.js
const server = require('umi-server');
const http = require('http');
const { createReadStream } = require('fs');
const { join, extname } = require('path');
const root = join(__dirname, 'dist');
const render = server({
root,
})
const headerMap = {
'.js': 'text/javascript',
'.css': 'text/css',
'.jpg': 'image/jpeg',
'.png': 'image/jpeg',
}
http.createServer(async (req, res) => {
const ext = extname(req.url);
const header = {
'Content-Type': headerMap[ext] || 'text/html'
}
res.writeHead(200, header);
if (!ext) {
// url render
const ctx = {
req,
res,
}
const { ssrHtml } = await render(ctx);
res.write(ssrHtml);
res.end()
} else {
// static file url
const path = join(root, req.url);
const stream = createReadStream(path);
stream.on('error', (error) => {
res.writeHead(404, 'Not Found');
res.end();
});
stream.pipe(res);
}
}).listen(3000)
console.log('http://localhost:3000');
运行 node bar.js
,访问 http://localhost:3000,就是一个简单的服务端渲染例子。详细可见 examples/normal
可参考 examples/koajs
可参考 examples/eggjs
预渲染(Pre Render)在构建时执行渲染,将渲染后的 HTML 片段生成静态 html 文件。无需使用 web 服务器实时动态编译 HTML,适用于静态站点。
Umi 提供 @umijs/plugin-prerender 插件,帮助用户在构建时预渲染出页面。更多用法参考文档。
export default {
plugins: [['@umijs/plugin-prerender', options]],
};
以上式本次我初学Umi的一个总结。只是总结了大部分的知识点,并没有太深的理解。后期会持续更新,思维导图和本文内容