鉴于 UmiJS 是重度的封装了很多工具和功能,对于刚接触的人形成了一层技术壁垒(大佬请忽略这句话)。所以我总结了使用UmiJS 开发中遇到的坑和技巧。
本地开发 umi dev 时关闭 mock
参考: 希望本地开发 umi dev 时可以关闭 mock
方案一(推荐):在 npm scripts 中加入以下指令:
1 2 3 4 5
| { "scripts": { "dev": "MOCK=none umi dev" } }
|
方案二:在 .env
文件里 设置 MOCK=none
也可以关闭
jsx无法转到定义处
在 jsconfig.json
文件中进行如下配置:
1 2 3 4 5
| { "compilerOptions": { "jsx": "react" } }
|
在umi中如何访问静态资源
在umi框架中,图片等静态资源主要放到三个地方:
- 在
/public
目录下,一般放共享资源。 - 在
/src/assets/
目录下,一般放全局静态资源。 - 在
/src/pages/
里的各个页面目录下,放在这里的好处是更符合组件化开发的思想,便于拷贝复用。
由于静态资源会受 context.config.publicPath
的影响,所以在 document.ejs
中应该这样引入比较安全:
1
| <script type="text/javascript" src="<%= context.config.publicPath %>ol.js" />
|
如何访问静态图片
1、如果在/public目录下的静态图片,可以直接输入绝对路径,假设/public/yay.jpg,访问方式如下:
<img src="/yay.jpg" />
注意:以上必须构建后在dist中才能看到。
2、在 /src/assets
和 /src/pages/
目录下的图片,不能通过输入绝对路径访问,必须先 import
导入,才能访问。或者 require
导入。比如 /src/assets/yay.jpg
需:
1 2 3 4
| import yayImg from '/src/assets/yay.jpg'; <img src={yayImg} />
<img src={require('/src/assets/yay.jpg')}
|
为什么会这样呢?主要是因为构建时,/public
目录下的文件会原样复制到 /dist/
目录下,而 /src/assets/
和 /src/pages/
目录下的文件会被改名并复制到 /dist/
下。
react-router三种传参方式
参考: react-router三种传参方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { Component } from 'react' import router from 'umi/router'
const RouterDemo = () => { const onOk = () => { router.push({ pathname: 'test/router', query: { id: '1', code: '123', }, params: { d: '1', code: '123', }, state: { d: '1', code: '123', }, }) } return <div onClick={this.onOk}>点击测试</div> }
|
修改浏览器上方图标
参考: HTML 配置模板
1 2 3 4
| <link rel="icon" type="image/x-icon" href="<%= context.publicPath %>favicon.png" />
<link rel="icon" type="image/x-icon" href="<%= context.publicPath %>static/favicon.png" />
|
支持 ie11
参考: ie11兼容问题
配置浏览器最低版本,会自动引入 polyfill 和做语法转换,配置的 targets 会和合并到默认值,所以不需要重复配置:
1 2 3 4 5 6 7
|
export default { targets: { ie: 11, }, };
|
编译 node_modules 下的包
UmiJS 2.x
参考: How to configure extraBabelIncludes
1 2 3 4
| const path = require('path'); { extraBabelIncludes: [path.resolve(__dirname, 'node_modules/<package_name>')], }
|
UmiJS 3.1+
参考: nodeModulesTransform、如何做编译提速
UmiJS 3 删除了 extraBabelIncludes
和 es5ImcompatibleVersions
,node_modules
也走 babel 编译后就没有意义了,无需配置
UmiJS 3 默认编译 node_modules
下的文件,带来一些收益的同时,也增加了额外的编译时间。如果不希望 node_modules
下的文件走 babel 编译,可通过以下配置减少 40% 到 60% 的编译时间。
1 2 3 4 5 6
| export default { nodeModulesTransform: { type: 'none', exclude: [], }, }
|
并行运行任务
call
参考: 求教多个异步的请求问题?、同时执行多个任务
yield
指令可以很简单的将异步控制流以同步的写法表现出来,但与此同时我们将也会需要同时执行多个任务,我们不能直接这样写:
1 2 3
| const users = yield call(fetch, '/users') const repos = yield call(fetch, '/repos')
|
由于第二个 effect 将会在第一个 call 执行完毕才开始。所以我们需要这样写:
1 2 3 4 5 6 7
| *effects({}, { all, call }) { const [users, repos] = yield all([ call(fetch, '/users'), call(fetch, '/repos') ]) }
|
当我们需要 yield
一个包含 effects 的数组, generator 会被阻塞直到所有的 effects 都执行完毕,或者当一个 effect 被拒绝 (就像 Promise.all
的行为)。
put
参考: yield all中放put而出现的问题
1 2 3 4 5 6
| *effects({}, { all, call }) { const [users, repos] = yield all([ yield put({ type: 'getUsers' }), yield put({ type: 'getRepos' }) ]) }
|
或者使用 put.resolve
:
1 2 3 4 5 6
| *effects({}, { all, call }) { const [users, repos] = yield all([ put.resolve({ type: 'getUsers' }), put.resolve({ type: 'getRepos' }) ]) }
|
局部覆盖antd 样式
由于业务的个性化需求,我们经常会遇到需要覆盖组件样式的情况,这里举个简单的例子。
antd Select 在多选状态下,默认会展示所有选中项,这里我们给它加一个限制高度,超过此高度就出滚动条。
1 2 3 4 5 6 7 8
| <Select mode="multiple" style={{ width: 300 }} placeholder="Please select" className={styles.customSelect} > {children} </Select>
|
1 2 3 4 5 6 7 8
| .customSelect { :global { .ant-select-selection { max-height: 51px; overflow: auto; } } }
|
方法很简单,有两点需要注意:
- 引入的 antd 组件类名没有被 CSS Modules 转化,所以被覆盖的类名
.ant-select-selection
必须放到 :global
中。 - 因为覆盖是全局性的。为了防止对其他 Select 组件造成影响,所以需要包裹额外的 className 限制样式的生效范围。
优化包大小
参考: H5 分包实现首屏加载时间优化、webapck4 玄妙的 SplitChunks Plugin、请问如何单独打包组件
UmiJS 2.x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| { ignoreMomentLocale: true, treeShaking: true, chainWebpack(config) { config.optimization.splitChunks({ chunks: 'all', automaticNameDelimiter: '~', name: true, minSize: 30000, minChunks: 1, cacheGroups: { echarts: { name: 'echarts', test: /[\\/]node_modules[\\/](echarts)[\\/]/, priority: -9, enforce: true, }, antd: { name: 'antd', test: /[\\/]node_modules[\\/](@ant-design|antd|antd-mobile)[\\/]/, priority: -10, enforce: true, }, vendors: { name: 'vendors', test: /[\\/]node_modules[\\/]/, priority: -11, enforce: true, }, }, }); }, plugins: [ [ 'umi-plugin-react', { chunks: ['vendors', 'antd', 'echarts', 'umi'], }, ], ], }
|
UmiJS 3.x
参考 升级 umi-plugin-react 为 @umijs/preset-react
由于 Umi 3 的配置方式是拍平的方式,还需要修改配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| { ignoreMomentLocale: true, treeShaking: true, chainWebpack(config) { config.optimization.splitChunks({ chunks: 'all', automaticNameDelimiter: '~', name: true, minSize: 30000, minChunks: 1, cacheGroups: { echarts: { name: 'echarts', test: /[\\/]node_modules[\\/](echarts)[\\/]/, priority: -9, enforce: true, }, antd: { name: 'antd', test: /[\\/]node_modules[\\/](@ant-design|antd|antd-mobile)[\\/]/, priority: -10, enforce: true, }, vendors: { name: 'vendors', test: /[\\/]node_modules[\\/]/, priority: -11, enforce: true, }, }, }); }, chunks: ['vendors', 'antd', 'echarts', 'umi'], }
|
momentjs
使用中文配置
参考: antd design国际化配置为中文时,日期组件中月与星期显示为英文,其他显示为中文
1 2 3 4 5 6 7 8 9
| import { LocaleProvider } from 'antd'; import zh_CN from 'antd/lib/locale-provider/zh_CN'; import moment from 'moment'; import 'moment/locale/zh-cn';
moment.locale('zh-cn'); ...
return <LocaleProvider locale={zh_CN}><App /></LocaleProvider>;
|
替换 momentjs
参考: antd-dayjs-webpack-plugin、替换 Moment.js、基于umi、antd的前端工程优化实践
请先删除 ignoreMomentLocale: true
配置再进行以下操作:
1
| yarn add antd-dayjs-webpack-plugin -D
|
1 2 3 4 5 6 7 8 9 10 11
| export default { chainWebpack(config) { config.plugin('moment2dayjs').use('antd-dayjs-webpack-plugin', [ { preset: 'antdv3' } ]) } }
|
如果项目中需要使用中文语言,还要引入dayjs的中文语言包并与antd的ConfigProvider配合服用。
1 2 3 4 5 6 7 8
| import dayjs from 'dayjs' import 'dayjs/locale/zh-cn' dayjs.locale('zh-cn')
import { ConfigProvider } from 'antd' import zhCN from 'antd/lib/locale-provider/zh_CN' export default ({children}) => <ConfigProvider locale={zhCN}>{children}</ConfigProvider>
|
通过上述配置后,使用DatePicker组件拿到的日期与之前一致,但可以直接使用dayjs的API操作日期,moment不复存在。最终dayjs打包体积为14.64KB,减小了330KB之多。
注:目前dayjs@1.8.20后有个bug会导致替换后WeekPicker显示不正常,1.8.21版本之后已修复。
dva-loading 使用
参考: dva-loading 实践用法
loading 分为四种使用情况,下面依次用代码展示:
1、全局
监听的是应用中所有 effect 是否执行完毕,若执行完毕。loading 的值就变为 false
。
1 2 3 4 5 6 7 8 9 10 11
| import React from 'react' const {useSelector,useDispatch} = 'dva' import {Spin} from 'antd' const DemoPage = () => { const {loading} = useSelector(stores => ({ loading: stores.loading })) return ( <Spin spinning={loading.global}/> ) }
|
2、model
监听某个模块的所有 effect 是否执行完毕,若执行完毕。loading 的值就变为 false
。
1 2 3 4 5 6 7 8 9 10 11 12
| import React from 'react' const {useSelector,useDispatch} = 'dva' import {Spin} from 'antd' const DemoPage = () => { const {loading,demoModel} = useSelector(stores => ({ loading: stores.loading, demoModel: stores.loading, })) return ( <Spin spinning={loading.models.demoModel}/> ) }
|
3、effect:
监听某个 effect 是否执行完毕,若执行完毕。loading 的值就变为 false
。
1 2 3 4 5 6 7 8 9 10 11 12
| import React from 'react' const {useSelector,useDispatch} = 'dva' import {Spin} from 'antd' const DemoPage = () => { const {loading,demoModel} = useSelector(stores => ({ loading: stores.loading, demoModel: stores.loading, })) return ( <Spin spinning={loading.effects['demoModel/effect1']/> ) }
|
4、effects
如果想监听某个 model 中的某几个 effect,可以使用 ||
连接,当全部执行完毕时,返回的是 undefined
,所以必须在末尾拼接 || false
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react' const {useSelector,useDispatch} = 'dva' import {Spin} from 'antd' const DemoPage = () => { const {loading,demoModel} = useSelector(stores => ({ loading: stores.loading, demoModel: stores.loading, })) return ( <Spin spinning={ loading.effects['demoModel/effect1'] || loading.effects['demoModel/effect3'] || loading.effects['demoModel/effect4'] || false } /> ) }
|
关闭 Umi UI
umi 项目默认启动 umi ui
,会出现一个mini图标气泡浮在右下角,关闭有两种方式,一种是直接用样式 display none
。另一种是在启动时加上 UMI_UI=none
环境变量。
1 2 3 4 5 6
| { "scripts": { "start": "UMI_UI=none umi dev", "dev": "MOCK=none UMI_UI=none umi dev", } }
|