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 编译太慢的错觉),有时想做些修改会感觉无所适从。但如果是想获得保姆级的开发体验,或许可以使用一下。但还是建议慎重,适用于阿里的不一定适用于你,毕竟出了问题他们可以内部解决,但你不能。
为什么就没有功能强大、轻量好用的框架呢?就像小刀,几乎不需要额外学习成本就可以使用。不过小刀好像不强大😕。