Ant Design Pro 是一個基於 umi、dva 和 ant design 的開箱即用的中台前端/設計解決方案。
一、umi和dva
1.1、什么是umi
umi,中文可發音為烏米,是一個可插拔的企業級 react 應用框架。umi 以路由為基礎的,支持類 next.js 的約定式路由,以及各種進階的路由功能,並以此進行功能擴展,比如支持路由級的按需加載。然后配以完善的插件體系,覆蓋從源碼到構建產物的每個生命周期,支持各種功能擴展和業務需求,目前內外部加起來已有 50+ 的插件。
上面一段來自umi官網介紹,從上面我們得到一個重要信息,umi的一個重要功能就是路由配置。 此外umi整合了webpack的功能,umi相比webpack增加了運行時的能力,同時幫助我們配置了很多 webpack 的預設。也減少了 webpack 升級導致的問題:
- plugin-access,權限管理
- plugin-analytics,統計管理
- plugin-antd,整合 antd UI 組件
- plugin-initial-state,初始化數據管理
- plugin-layout,配置啟用 ant-design-pro 的布局
- plugin-locale,國際化能力
- plugin-model,基於 hooks 的簡易數據流
- plugin-request,基於 umi-request 和 umi-hooks 的請求方案
1.2、什么是dva
dva 首先是一個基於 redux 和 redux-saga 的數據流方案,可以和umi完美結合。
1.3、 什么是redux-saga
redux-saga是一個用於管理應用程序 Side Effect(副作用,例如異步獲取數據,訪問瀏覽器緩存等)的 library,它的目標是讓副作用管理更容易,執行更高效,測試更簡單,在處理故障時更容易。
可以想像為,一個 saga 就像是應用程序中一個單獨的線程,它獨自負責處理副作用。redux-saga是一個 redux 中間件,意味着這個線程可以通過正常的 redux action 從主應用程序啟動,暫停和取消,它能訪問完整的 redux state,也可以 dispatch redux action。
redux-saga 使用了 ES6 的 Generator 功能,讓異步的流程更易於讀取,寫入和測試。(如果你還不熟悉的話,這里有一些介紹性的鏈接) 通過這樣的方式,這些異步的流程看起來就像是標准同步的 Javascript 代碼。(有點像 async
/await
,但 Generator 還有一些更棒而且我們也需要的功能)。
你可能已經用了 redux-thunk 來處理數據的讀取。不同於 redux thunk,你不會再遇到回調地獄了,你可以很容易地測試異步流程並保持你的 action 是干凈的。
1.3.1 安裝
npm install --save redux-saga
1.3.2 使用示例
假設我們有一個 UI 界面,在單擊按鈕時從遠程服務器獲取一些用戶數據(為簡單起見,我們只列出 action 觸發代碼)。
class UserComponent extends React.Component { ... onSomeButtonClicked() { const { userId, dispatch } = this.props dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}}) } ... }
這個組件 dispatch 一個 plain Object 的 action 到 Store。我們將創建一個 Saga 來監聽所有的 USER_FETCH_REQUESTED action,並觸發一個 API 調用獲取用戶數據。
sagas.js:
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects' import Api from '...'
// worker Saga : 將在 USER_FETCH_REQUESTED action 被 dispatch 時調用
function* fetchUser(action) { try { const user = yield call(Api.fetchUser, action.payload.userId); yield put({type: "USER_FETCH_SUCCEEDED", user: user}); } catch (e) { yield put({type: "USER_FETCH_FAILED", message: e.message}); } } /* 在每個 `USER_FETCH_REQUESTED` action 被 dispatch 時調用 fetchUser 允許並發(譯注:即同時處理多個相同的 action) */
function* mySaga() { yield takeEvery("USER_FETCH_REQUESTED", fetchUser); } /* 也可以使用 takeLatest 不允許並發,dispatch 一個 `USER_FETCH_REQUESTED` action 時, 如果在這之前已經有一個 `USER_FETCH_REQUESTED` action 在處理中, 那么處理中的 action 會被取消,只會執行當前的 */
function* mySaga() { yield takeLatest("USER_FETCH_REQUESTED", fetchUser); } export default mySaga;
為了能跑起 Saga,我們需要使用 redux-saga 中間件將 Saga 與 Redux Store 建立連接。
main.js:
import { createStore, applyMiddleware } from 'redux' import createSagaMiddleware from 'redux-saga' import reducer from './reducers' import mySaga from './sagas'
// create the saga middleware
const sagaMiddleware = createSagaMiddleware() // mount it on the Store
const store = createStore( reducer, applyMiddleware(sagaMiddleware) ) // then run the saga
sagaMiddleware.run(mySaga) // render the application
更多使用細節請移步官網。
二、腳手架使用
2.1、初始化腳手架
新建一個空的文件夾作為項目目錄,並在目錄下使用 create umi 來快速的初始化腳手架。
# 使用 npm npm create umi react-big-screen
如果下載比較慢,可以先配置npm阿里源:
npm config set registry https://registry.npm.taobao.org/
按照 umi 腳手架的引導,第一步先選擇 ant-design-pro:
? Select the boilerplate type (Use arrow keys) > ant-design-pro - Create project with a layout-only ant-design-pro boilerplate, use together with umi block. app - Create project with a simple boilerplate, support typescript. block - Create a umi block. library - Create a library with umi. plugin - Create a umi plugin.
選擇 antd 的版本,4 或者 5,主意V5和V4差別較大,這里我選擇了V5。
? Select the boilerplate type ant-design-pro ? � Be the first to experience the new umi@3 ?
> Pro V5 Pro V4
安裝依賴:
cd react-big-screen && cnpm install
2.2、啟動腳手架
開啟 Umi UI(可選):
npm start
使用admin/ant.design登錄系統:
通過npm start我們就可以啟動服務,那你可能會好奇執行這個命令為什么就可以啟動我們的服務呢?
npm start 運行的是package.json中script塊start對應的命令,打開package.json,你會發現我們實際執行的是如下執行:
cross-env是用來解決多環境問題的,你可暫且忽略。重點關注umi dev。
umi 是一個軟連接到umi.js的文件:
#!/usr/bin/env node
const resolveCwd = require('resolve-cwd'); const { name, bin } = require('../package.json'); const localCLI = resolveCwd.silent(`${name}/${bin['umi']}`);
if (!process.env.USE_GLOBAL_UMI && localCLI && localCLI !== __filename) { const debug = require('@umijs/utils').createDebug('umi:cli'); debug('Using local install of umi'); require(localCLI); } else { require('../lib/cli'); }
也就是其實執行的umi/lib/cli.js文件, 關鍵部分代碼:
_asyncToGenerator(function* () { try { switch (args._[0]) { case 'dev': const child = (0, _fork.default)({ scriptPath: require.resolve('./forkedDev') }); // ref:
// http://nodejs.cn/api/process/signal_events.html
// https://lisk.io/blog/development/why-we-stopped-using-npm-start-child-processes
process.on('SIGINT', () => { child.kill('SIGINT'); // ref:
// https://github.com/umijs/umi/issues/6009
process.exit(0); }); process.on('SIGTERM', () => { child.kill('SIGTERM'); process.exit(1); }); break; default: const name = args._[0]; if (name === 'build') { process.env.NODE_ENV = 'production'; } yield new _ServiceWithBuiltIn.Service({ cwd: (0, _getCwd.default)(), pkg: (0, _getPkg.default)(process.cwd()) }).run({ name, args }); break; } } catch (e) { console.error(_utils().chalk.red(e.message)); console.error(e.stack); process.exit(1); } })();
三、頁面布局調整
運行程序,我們發現菜單默認是位於側邊欄的,如果我們想將菜單是置於頂部的。因此我們需要進行布局的修改。
在修改之前,我們了解到為了降低研發成本,Ant Design Pro將布局通過umi插件 @umijs/plugin-layout 的方式內置,我們只需要通過簡單的配置就可以修改Ant Design的Layout,包括導航以及側邊欄。
plugin-layout 插件主要提供了以下功能:
- 默認為 Ant Design 的 Layout @ant-design/pro-layout,支持它全部配置項。
- 側邊欄菜單數據根據路由中的配置自動生成。
- 默認支持對路由的 403/404 處理和 Error Boundary。
- 搭配 @umijs/plugin-access 插件一起使用,可以完成對路由權限的控制。
- 搭配 @umijs/plugin-initial-state 插件和 @umijs/plugin-model 插件一起使用,可以擁有默認用戶登陸信息的展示。
3.1、修改config/config.ts
可以通過修改配置文件配置 layout的主題、菜單等樣式, 修改config/config.ts:
import { Settings as LayoutSettings } from '@ant-design/pro-layout'; //pro-layout布局配置重寫,更多參數配置參考;https://github.com/ant-design/ant-design-pro-layout
const Settings: LayoutSettings & { pwa?: boolean; logo?: string; } = { navTheme: 'dark', //整體風格設置 light、dark
// 拂曉藍
primaryColor: '#1890ff', //主題色
layout: 'top', //導航模式 side、top、mix
contentWidth: 'Fluid', //內容區域寬度:流式Fluid、定寬Fixed
fixedHeader: false, //固定header
fixSiderbar: true, //固定側邊測但
colorWeak: false, //色弱模式
title: '報警管理平台', pwa: false, logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', iconfontUrl: '', }; export default Settings;
此時,頁面布局如下:
3.2、pro-components
ant-design/pro-layout 已經遷移至 ant-design/pro-components 倉庫進行后續的維護,訪問 https://procomponent.ant.design/layout 可以了解更多信息。
ProComponents 是基於 Ant Design 而開發的模板組件,提供了更高級別的抽象支持,開箱即用。其中ProComponets封裝了頁面布局相關的組件,這些組件來自於ant-design/pro-layout ,主要包括ProLayout、PageContainer、GridContent、FooterToolbar、SettingDrawer、SiderMenu、TopNavHeader等。
而我們配置的config/config.ts這些信息,會被這些布局組件所使用。具體實現可以查看源碼。
3.3、SettingDrawer(只針對V5版本,其他把版本查看官方文檔)
從官網下載的代碼默認是不支持主題切換的,如果想支持主題切換需要安裝以下依賴:
cnpm install umi-plugin-setting-drawer --save-dev cnpm install umi-plugin-antd-theme --save-dev
最新版本如下:
"umi-plugin-antd-theme": "^2.1.2", "umi-plugin-setting-drawer": "^1.0.3"
同時需要配置主題相關信息config/theme.config.json:
{ "theme": [ { "key": "dark", "fileName": "dark.css", "theme": "dark", "modifyVars": { "dark": true } }, { "key": "dust", "fileName": "dust.css", "modifyVars": { "@primary-color": "#F5222D" } }, { "key": "volcano", "fileName": "volcano.css", "modifyVars": { "@primary-color": "#FA541C" } }, { "key": "sunset", "fileName": "sunset.css", "modifyVars": { "@primary-color": "#FAAD14" } }, { "key": "cyan", "fileName": "cyan.css", "modifyVars": { "@primary-color": "#13C2C2" } }, { "key": "green", "fileName": "green.css", "modifyVars": { "@primary-color": "#52C41A" } }, { "key": "geekblue", "fileName": "geekblue.css", "modifyVars": { "@primary-color": "#2F54EB" } }, { "key": "purple", "fileName": "purple.css", "modifyVars": { "@primary-color": "#722ED1" } }, { "key": "dust", "theme": "dark", "fileName": "dark-dust.css", "modifyVars": { "@primary-color": "#F5222D", "dark": true } }, { "key": "volcano", "theme": "dark", "fileName": "dark-volcano.css", "modifyVars": { "@primary-color": "#FA541C", "dark": true } }, { "key": "sunset", "theme": "dark", "fileName": "dark-sunset.css", "modifyVars": { "@primary-color": "#FAAD14", "dark": true } }, { "key": "cyan", "theme": "dark", "fileName": "dark-cyan.css", "modifyVars": { "@primary-color": "#13C2C2", "dark": true } }, { "key": "green", "theme": "dark", "fileName": "dark-green.css", "modifyVars": { "@primary-color": "#52C41A", "dark": true } }, { "key": "geekblue", "theme": "dark", "fileName": "dark-geekblue.css", "modifyVars": { "@primary-color": "#2F54EB", "dark": true } }, { "key": "purple", "theme": "dark", "fileName": "dark-purple.css", "modifyVars": { "@primary-color": "#722ED1", "dark": true } } ], "min": true, "isModule": true, "ignoreAntd": false, "ignoreProLayout": false, "cache": true, "extraLibraries": [ "@ant-design/pro-form", "@ant-design/pro-descriptions", "@ant-design/pro-field", "@ant-design/pro-list", "@ant-design/pro-card", "@ant-design/pro-provider", "@ant-design/pro-skeleton", "@ant-design/pro-utils" ] }
-
extraLibraries:@antd-design/pro-xxx ; antd-pro-merge-less 在生成樣式文件時,會加載該指定包中樣式文件(特別需要注意的是antd-pro-merge-less 默認只加載了antd pro組件 @ant-design/pro-table、@ant-design/pro-form的樣式);
-
ignoreAntd:在生成樣式文件時,是否加載antd樣式文件
-
cache:是否開啟緩存,通過比較樣式和antd-pro-merge-less/.temp/temp.less文件的hash,判斷文件是否需要重新生成,這樣就不會在每次啟動的時候重新生成樣式文件
umi-plugin-antd-theme會根據遍歷這個配置文件,動態的在node_modules\.plugin-theme\theme下生成的對應的樣式文件。
最終在點擊SettingDrawer組件進行樣式調整的時候,實際就是給動態body加入樣式文件的過程 :
<link type="text/css" rel="stylesheet" id="theme-style" href="/theme/volcano.css">
我們也可以將樣式文件復制到/public/theme/下。
如果我們不想使用SettingDrawer組件,需要把package.json devDependencies中的這兩個包排除。
SettingDrawer組件中的主題色ThemeColor 用於展示當前可選的主色,色彩列表themeList由 umi-plugin-antd-theme 插件提供,該插件會將一個色彩列表放到 window.umi_plugin_ant_themeVar 變量上,SettingDrawer 會讀取這個變量並給到 ThemeColor。
<ThemeColor value={primaryColor} colors={ hideColors ? [] : themeList.colorList[navTheme === 'realDark' ? 'dark' : 'light'] } formatMessage={formatMessage} onChange={(color) => changeSetting('primaryColor', color, hideLoading) } />
3.4、 umi-plugin-antd-theme 插件流程分析
這里只粗略介紹一下具體流程、不做細致的代碼分析,感興趣的請閱讀源碼。
-
更改 cssLoader 配置,修改 src 目錄下less 文件CSS Modules選擇器名稱
-
加載工程固定路徑下主題配置文件 config/theme.config.json覆蓋默認配置(如果配置為空,也就是使用組件原始配置樣式)
-
設置dev環境臨時主題色文件路徑為 node_modules/.plugin-theme/theme
-
設置serve-static中間件,允許訪問臨時主題色文件
-
將主題色配置信息掛載在 window.umi_plugin_ant_themeVar
-
dev環境 onDevCompileDone
如果存在臨時文件,則刪除
創建 .plugin-theme/theme
遍歷config/theme.config.json下每個主題色,使用 antd-pro-merge-less包 buildCss 至 .plugin-theme/theme 每一個主題色,生成一個 css 文件 prod 環境與 dev 環境的差異體現在生產的文件生成在 dist/theme 目錄下
3.5、antd-pro-merge-less 插件流程分析
-
設置臨時文件目錄antd-pro-merge-less/.temp
-
使用 glob 找到 antd-pro-merge-less 項目下所有的 less 文件路徑
-
將所有less文件內容寫入antd-pro-merge-less/.temp/temp.less以及antd-pro-merge-less/.temp/pro.less
-
如果config/theme.config.json配置"ignoreAntd": false,則還會導入antd模塊下的所有less文件(需要注意的是如果沒有配置dark:true, 不會加載themes/dark.less,themes/compack.less),到 ./antd.less
-
將@import './antd'導入./components.less文件,同時還會根據extraLibraries配置導入antd design pro組件的樣式文件
-
提取import文件的變量,刪除 import ,寫入antd-pro-merge-less/.temp/pro.less ,並引用 @import './components'
-
將依賴的 antd 相關組件的 less 文件寫入./components.less 開始遍歷不同的顏色配置,根據不同配置生成 antd less 文件
-
使用 less 將 pro.less 文件轉化為 css 文件,並替換變量
常見錯誤處理:
1、如果運行出現錯誤 檢查node_modules\_antd-pro-merge-less@xxx@antd-pro-merge-less\loopAllLess.js文件,修改文件最后為:
return Promise.resolve(
// prettier.format(content, {
// parser: 'less',
// }),
content
);
因為prettier.format在格式化less文件時會將 :
@pro-global-footer-prefix-cls: ~'@{ant-prefix}-pro-global-footer' 轉為
@pro-global-footer-prefix-cls ~'@{ant-prefix}-pro-global-footer'
2、@ant-deisign/pro-card組件存在Bug _@ant-design_pro-card@1.11.7@@ant-design\pro-card\es\components\Statistic\index.less
.@{ant-prefix}-statistic-title { color: @text-color; # 將之前的固定色rgba(00,00,00,0.45)修改成變量 }
3、強制重新生成樣式文件
- 如果config/theme.config.json配置中開啟了緩存cache,如果想強制重新生成樣式文件,刪除antd-pro-merge-less\.temp\temp.less文件,重新運行
- 設置cache為false關閉緩存
直接在V5的Demo頁面多次點擊菜單來切換路由,會發現樣式會有多次覆蓋的情況。
https://preview.pro.ant.design/dashboard/analysis?fixSiderbar=true&colorWeak=false&pwa=false
目前是參考 https://umijs.org/zh-CN/config#chainwebpack ,修改了打包規則,config.ts中增加以下配置,解決了這個問題:
chunks: ['vendors', 'umi'], chainWebpack: function (config, { webpack }) { config.merge({ optimization: { splitChunks: { chunks: 'all', minChunks: 2, automaticNameDelimiter: '.', cacheGroups: { vendor: { name: 'vendors', test({ resource }) { return /[\\/]node_modules[\\/]/.test(resource); }, priority: 10, }, }, }, }, }); },
四、新增頁面步驟
這里的『頁面』指配置了路由,能夠通過鏈接直接訪問的模塊,要新建一個頁面,通常只需要在腳手架的基礎上進行簡單的配置。
4.1、新增js、less文件
在src/pages下創建新的js、less文件。如果又多個相關頁面,可以創建一個新文件夾來放置相關文件。
config src models pages + NewPage.js + NewPage.less ... ... package.json
為了更好的演示,我們初始化NewPage.js的內容如下:
export default () => { return <div>New Page</div>;
};
暫時不向本文檔中的樣式文件添加內容,您也可以嘗試自己添加內容。 樣式文件默認使用CSS Modules,如果需要,可以導入antd less 變量 在文件的頭部:
@import '~antd/lib/style/themes/default.less';
這樣可以輕松獲取 antd 樣式變量並在文件中使用它們,這可以保持保持頁面的一致性,並有助於實現自定義主題。
4.2、將文件加入菜單和路由
加入菜單和路由的方式請參照 路由和菜單 - 添加路由/菜單 中的介紹完成。加好后,訪問 http://localhost:8000/#/new 就可以看到新增的頁面了。
4.3、新增model、service
布局及路由都配置好之后,回到之前新建的 NewPage.js,可以開始寫業務代碼了!
如果需要用到 dva 中的數據流,還需要在 src/models src/services 中建立相應的 model 和 service,具體可以參考腳手架內置頁面的寫法。不過我們更加推薦使用輕量數據流管理數據,並且使用 openAPI 插件來生成 serves。
五、代碼下載
5.1、開發計划
接下來我將花一段時間開發一個監控平台: