UmiJS開發技巧
鑒於 UmiJS 是重度的封裝了很多工具和功能,對於剛接觸的人形成了一層技術壁壘(大佬請忽略這句話)。所以我總結了使用UmiJS 開發中遇到的坑和技巧。
本地開發 umi dev 時關閉 mock
方案一(推薦):在 npm scripts 中加入以下指令:
{
"scripts": { "dev": "MOCK=none umi dev" } } 復制代碼
方案二:在 .env
文件里 設置 MOCK=none
也可以關閉
jsx無法轉到定義處
在 jsconfig.json
文件中進行如下配置:
{
"compilerOptions": { "jsx": "react" } } 復制代碼
在umi中如何訪問靜態資源
在umi框架中,圖片等靜態資源主要放到三個地方:
- 在
/public
目錄下,一般放共享資源。 - 在
/src/assets/
目錄下,一般放全局靜態資源。 - 在
/src/pages/
里的各個頁面目錄下,放在這里的好處是更符合組件化開發的思想,便於拷貝復用。
由於靜態資源會受 context.config.publicPath
的影響,所以在 document.ejs
中應該這樣引入比較安全:
<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
需:
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三種傳參方式
import { Component } from 'react' import router from 'umi/router' const RouterDemo = () => { const onOk = () => { router.push({ pathname: 'test/router', // 點擊之后,頁面會跳轉且地址上會跟上query的參數,?id=1&code=123 // 獲取方式傳值內容的方式: this.props.location.query query: { id: '1', code: '123', }, // 點擊之后,頁面會跳轉 // 通過this.props.location.params可以獲得params的值 // params可以為其他名字,如text、nihao等,不一定是params // 刷新頁面后,params的值會丟失。 params: { d: '1', code: '123', }, // 使用state傳值和params傳值一樣,都不會再url中顯示出來 // state傳值與params傳值的區別是state傳值刷新頁面值還在,而使用params傳值刷新后值不沒有了。 state: { d: '1', code: '123', }, }) } return <div onClick={this.onOk}>點擊測試</div> } 復制代碼
修改瀏覽器上方圖標
參考: HTML 配置模板
<!-- 圖片在 /public 下 --> <link rel="icon" type="image/x-icon" href="<%= context.publicPath %>favicon.png" /> <!-- 圖片在 /src/assets/ 下--> <link rel="icon" type="image/x-icon" href="<%= context.publicPath %>static/favicon.png" /> 復制代碼
支持 ie11
參考: ie11兼容問題
配置瀏覽器最低版本,會自動引入 polyfill 和做語法轉換,配置的 targets 會和合並到默認值,所以不需要重復配置:
// umi默認兼容最低瀏覽器版本 // Default: { chrome: 49, firefox: 45, safari: 10, edge: 13, ios: 10 } export default { targets: { ie: 11, }, }; 復制代碼
編譯 node_modules 下的包
UmiJS 2.x
const path = require('path'); { extraBabelIncludes: [path.resolve(__dirname, 'node_modules/<package_name>')], } 復制代碼
UmiJS 3.1+
UmiJS 3 刪除了 extraBabelIncludes
和 es5ImcompatibleVersions
,node_modules
也走 babel 編譯后就沒有意義了,無需配置
UmiJS 3 默認編譯 node_modules
下的文件,帶來一些收益的同時,也增加了額外的編譯時間。如果不希望 node_modules
下的文件走 babel 編譯,可通過以下配置減少 40% 到 60% 的編譯時間。
export default { nodeModulesTransform: { type: 'none', exclude: [], // 忽略的依賴庫,包名,暫不支持絕對路徑;可通過 exclude 配置添加額外需要編譯的 }, } 復制代碼
並行運行任務
call
參考: 求教多個異步的請求問題?、同時執行多個任務
yield
指令可以很簡單的將異步控制流以同步的寫法表現出來,但與此同時我們將也會需要同時執行多個任務,我們不能直接這樣寫:
// 錯誤寫法,effects 將按照順序執行 const users = yield call(fetch, '/users') const repos = yield call(fetch, '/repos') 復制代碼
由於第二個 effect 將會在第一個 call 執行完畢才開始。所以我們需要這樣寫:
// 正確寫法, effects 將會同步執行 *effects({}, { all, call }) { const [users, repos] = yield all([ call(fetch, '/users'), call(fetch, '/repos') ]) } 復制代碼
當我們需要 yield
一個包含 effects 的數組, generator 會被阻塞直到所有的 effects 都執行完畢,或者當一個 effect 被拒絕 (就像 Promise.all
的行為)。
put
*effects({}, { all, call }) { const [users, repos] = yield all([ yield put({ type: 'getUsers' }), yield put({ type: 'getRepos' }) ]) } 復制代碼
或者使用 put.resolve
:
*effects({}, { all, call }) { const [users, repos] = yield all([ put.resolve({ type: 'getUsers' }), put.resolve({ type: 'getRepos' }) ]) } 復制代碼
局部覆蓋antd 樣式
由於業務的個性化需求,我們經常會遇到需要覆蓋組件樣式的情況,這里舉個簡單的例子。
antd Select 在多選狀態下,默認會展示所有選中項,這里我們給它加一個限制高度,超過此高度就出滾動條。
<Select
mode="multiple" style={{ width: 300 }} placeholder="Please select" className={styles.customSelect} > {children} </Select> 復制代碼
.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
{
// 忽略 moment 的 locale 文件,用於減少尺寸。 // https://v2.umijs.org/zh/config/#ignoremomentlocale ignoreMomentLocale: true, // 配置是否開啟 treeShaking,默認關閉。 // https://v2.umijs.org/zh/config/#treeshaking treeShaking: true, // 通過 [webpack-chain](https://github.com/mozilla-neutrino/webpack-chain) 的 API 擴展或修改 webpack 配置。 // https://v2.umijs.org/zh/config/#chainwebpack 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: [ [ // 這是官方封裝的一個插件集,包含 18 個常用的進階功能。 // https://v2.umijs.org/zh/plugin/umi-plugin-react.html#%E5%AE%89%E8%A3%85 'umi-plugin-react', { // 默認是 ['umi'],可修改,比如做了 vendors 依賴提取之后,會需要在 umi.js 之前加載 vendors.js // https://v2.umijs.org/zh/plugin/umi-plugin-react.html#chunks chunks: ['vendors', 'antd', 'echarts', 'umi'], }, ], ], } 復制代碼
UmiJS 3.x
由於 Umi 3 的配置方式是拍平的方式,還需要修改配置:
{
// 忽略 moment 的 locale 文件,用於減少尺寸。 // https://v2.umijs.org/zh/config/#ignoremomentlocale ignoreMomentLocale: true, // 配置是否開啟 treeShaking,默認關閉。 // https://v2.umijs.org/zh/config/#treeshaking treeShaking: true, // 通過 [webpack-chain](https://github.com/mozilla-neutrino/webpack-chain) 的 API 擴展或修改 webpack 配置。 // https://v2.umijs.org/zh/config/#chainwebpack 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, }, }, }); }, // https://umijs.org/zh-CN/config#chunks chunks: ['vendors', 'antd', 'echarts', 'umi'], } 復制代碼
momentjs
使用中文配置
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
配置再進行以下操作:
yarn add antd-dayjs-webpack-plugin -D
復制代碼
export default { chainWebpack(config) { // antd moment -> dayjs // 如果在 Ant Design 3.x 的項目中使用本插件,需要傳入以下配置,指定 preset。 config.plugin('moment2dayjs').use('antd-dayjs-webpack-plugin', [ { preset: 'antdv3' } ]) } } 復制代碼
如果項目中需要使用中文語言,還要引入dayjs的中文語言包並與antd的ConfigProvider配合服用。
// 設置dayjs中文 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
。
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
。
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
。
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
:
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
環境變量。
{
"scripts": { "start": "UMI_UI=none umi dev", "dev": "MOCK=none UMI_UI=none umi dev", } } 復制代碼
配置多環境
1、安裝 cross-env 插件: yarn add cross-env -D
2、在 .umirc.js
文件中添加 define
export default { define: { // 添加這個自定義的環境變量 // 本地開發環境:dev,test環境:test,生產環境:prod "process.env.PRO_ENV": process.env.PRO_ENV }, } 復制代碼
3、package.json 添加 npm scripts
{
"scripts": { "start": "cross-env PRO_ENV=dev umi dev", "test": "cross-env PRO_ENV=test umi dev", "build": "cross-env PRO_ENV=prod umi build", } } 復制代碼
本文首發於楊俊寧的博客,創作不易,您的點贊👍是我堅持的動力