Table of Contents
根据官网介绍,umijs 是围绕 react 搭建的企业级开发框架。它有诸多优点:可扩展、开箱即用……
在看了基础的介绍之后,可以感觉到 umi 和 next 很相似。实际上官网也说了,umi 有参考 next。好的的设计相互借鉴可以少走很多弯路,这还是很好的。
umi 自动集成了很多插件,比如 antd、dva 等等。确实是开箱即用的。但对于一个新事物(我是指我新学习),我不太敢大规模应用。不过此次项目只有几个页面,试试 umi 未尝不可,谁让公司自己的组件库不更新呢。
因为之前公司项目用的是 react15.x、antd2.x,也没用 ts,所以直接面对 react16.x、antd4.x、ts 还是有点磕磕碰碰。
请求封装
umi 自带的 request 就是 promise 形式的,直接使用也很方便,但我还是习惯于再封装一层,也是为了把之前项目的习惯搬过来。封装主要有两点,一个是 token 等验证信息的填充。还有一个是请求结果的判定。如果没有权限,需要自动退出。所有结果都是 reject——为了方便业务代码种的 yield,其余判定也交给具体的业务请求逻辑。
另外,使用中发现接口错误会有两次报错提示。其中一次是我业务代码里写的,还有一次找了很久才发现是 umi 自带的😔。可能我文档看得不仔细,但是不管文档里有没有写,我觉得框架默认打开自带的接口报错提示不太好。既然它默认打开,我也只能手动关闭了。
// app.ts import { RequestConfig, ErrorShowType } from 'umi' // 关闭统一错误处理 export const request: RequestConfig = { errorConfig: { adaptor: resData => { return { ...resData, showType: ErrorShowType.SILENT } } } }
路由拦截
根据文档的介绍——可以在 app.ts 里写个 render 函数进行拦截。但在实际使用后发现这个函数好像只执行了一次(应用第一次渲染),并且我不知道如何在此判断路由是否存在。这跟我认识中的路由拦截不太一样。
所以,最好还是在 layout 里进行拦截。就像在路由组件外面再包裹一层 PrivateRouter 组件一样(之前项目是这样处理的)。不过现在这个项目不大,权限要求也不高,将就着 app.ts 里拦截也可以。
antd 默认英文
毕竟是要进军全球的企业,组件库默认英文。但好歹提供了配置。
// .umirc.ts import { defineConfig } from 'umi' export default defineConfig({ locale: { // 写全,否则不生效 default: 'zh-CN', antd: true, baseNavigator: false }, }) // src/locales/zh-CN.ts export default {}
用 updeep 代替 immer
immer 的使用方式并不是很合我心,还是习惯用 updeep。但是,updeep 和 immer 有冲突,所以需要在配置文件里关闭 immer。
// .umirc.ts export default defineConfig({ dva: { immer: false, // updeep 和 immer 冲突 }, })
然后,在 dva 里就可以愉快地使用 updeep 了。
import { Dispatch, History, Effect } from 'umi' import { EffectsCommandMap } from 'dva' import { AnyAction } from 'redux' import { message } from 'antd' import u from 'updeep' export interface YsAccountsState { dataSource: Array<object> } const namespace = 'ysAccounts' export default { //…… effects: { // 获取列表 *_page({ payload }: AnyAction, { call, put, select }: EffectsCommandMap) { //…… if (res && res.result === 0) { yield put({ type: 'updateState', payload: { dataSource: res.data.rows || [], }, }) } else { message.error(res?.msg || '获取列表失败') } }, } reducers: { updateState(state: YsAccountsState, action: AnyAction) { return u(action.payload, state) }, }, }
SvgIcon
react16 支持导入 svg 图标作为组件使用,这也是我使用 umi 的原因之一。不过,图标不能使用中文名称。
import React, { Component } from 'react' import { ReactComponent as Block1Svg } from '@/icon/svg/block-1.svg' import styles from './styles.less' class RightContent extends Component<PropTypes, StateProps> { constructor(props: PropTypes) { super(props) this.state = { controlList: [ { component: <Block1Svg />, key: 'block1', } ], } } render() { let { controlList } = this.state return ( controlList.map((item, index) => { return <div className={styles.controlItem} key={index}>{item.component}</div> } ) } }
// styles.less .controlItem { color: red; path { fill: currentColor; } }
修改 HTML 模板
在使用 webpack 之类打包工具工程化开发的情况下,不建议修改 HTML 模板。但是有实际情况又需要修改。很多框架也提供了修改方法。umi 也不例外。
<!doctype html> <html> <head> <meta charSet="utf-8" /> <title>视频监管</title> <script src="./js/jquery.min.js" ></script> </head> <body> <div id="root"></div> </body> </html>
TypeScript
虽然有部分人(包括我)不喜欢 TypeScript,但胳膊拧不过大腿,不紧跟潮流以后很难发展。初次使用,想严格遵守规则有点难,毕竟项目逾期就是我的问题了。所以,有些地方还是用了 any。比如 window 对象不存在某个属性的问题。
(<any>window)?.staffInfo?.tenantId
window 问题除了使用 any 大法,还可以修改根目录下 typings.d.ts 解决。
declare module '*.css'; declare module '*.less'; declare module "*.png"; declare module '*.svg' { export function ReactComponent(props: React.SVGProps<SVGSVGElement>): React.ReactElement const url: string export default url } interface Window { staffInfo?: object adminCompany?: object, mapCenter?: Array<number> }
ps:把灵活简约的 JavaScript 变成臃肿蹩脚的 TypeScript,为什么不直接写 c# 呢?
部署
按照文档,部署到非根目录需要配置 base。但是,这个配置只是解决 history 模式路径前缀的。比如 base 是 /foo/,本地路由是 /bar,那么线上路径就是 /foo/bar。对于打包后的文件引用路径,需要配置 publicPath。否则文件引用还是指向根目录。
我的项目不需要使用 history 模式,直接 hash 就行。所以,我不需要配置 base。
// .umirc.js export default defineConfig({ publicPath: './', history: { type: 'hash' } }
打包分析优化
开启 analyze
按照官方的说法,使用 ANALYZE umi build 就可以了。但我把 build 命令改了之后报错了。在 issue 查了一下,发现因为是 umi3.x 的关系,需要先安装 cross-env,命令改成这样:cross-env ANALYZE=1 umi build。
优化 moment
moment 包默认自带所有语言,所以体积很大。所以修改了下 .umirc.js 配置:
// .umirc.js import { defineConfig } from 'umi' export default defineConfig({ // …… chainWebpack(memo, { env, webpack, createCSSRule}) { memo .plugin('ContextReplacementPlugin') .use(webpack.ContextReplacementPlugin, [/moment[\/\\]locale$/, /zh-cn/]) .end() }, // …… }
总结
简单使用的体验并不是很好,封装了太多,也比较重量级(也许是 typescript 编译太慢的错觉),有时想做些修改会感觉无所适从。但如果是想获得保姆级的开发体验,或许可以使用一下。但还是建议慎重,适用于阿里的不一定适用于你,毕竟出了问题他们可以内部解决,但你不能。
为什么就没有功能强大、轻量好用的框架呢?就像小刀,几乎不需要额外学习成本就可以使用。不过小刀好像不强大😕。