咱們目前採用的是antd + react(umi)的框架作業務開發。在業務開發過程當中會有較多頻繁出現而且類似度很高的場景,好比基於一個table的基礎的增刪改查,這個相信你們都很是熟悉。在接到一個新的業務需求的時候,相信有很多人會選擇copy一份功能相似的代碼而後基於這份代碼去改造以知足當前業務,固然我目前也是這樣作的~html
其實想把這塊功能提取成一個公共組建的想法由來已久,最近開始作基礎組件,便拿這個下手了。通過一週左右的時間完成了基礎組件的編寫。react
查看基礎支持的功能點API。git
基本的思路是經過json生成一些抽象配置,而後經過解析json的抽象配置+渲染器最終生成頁面。json配置涵蓋了目前80%的業務場景的基本需求,可是可擴展性很低。好比一些複雜的業務場景:表單的關聯校驗
、數據關聯顯示
、多級列表下鑽
等等功能。雖然經過一些較爲複雜的處理能夠把這些功能融入進來,但最終組件將會異常龐大難以維護。github
因此,我能不能經過這些json配置經過某種工具生成對應的代碼?這樣一來以上提到的問題就徹底不存在了,由於這和咱們本身寫的代碼徹底同樣,工具只是幫咱們完成初始化的過程。因此後來想了不少辦法,最初採用template string
的方式,這種方式較爲簡單粗暴,無非經過string中嵌套變量的判斷來輸出code。可是在實際寫的時候發現不少問題,好比express
function
的輸出(JSON.stringify會將function忽略)最終學習了一些生成代碼的工具好比angular-cli以及一些關於js生成代碼的文章,主要是經過知乎上的這篇討論瞭解到了你們是怎麼處理這種問題的。最終決定採用babel的生態鏈來解決上述遇到的問題。json
咱們目前採用的方式是基於antd+react(umi)編寫通用的CRUD模板,而後經過代碼生成器解析json中的配置生成對應的代碼,大體的流程是:後端
React --> JavaScript AST ---> Code Generator --> Compiler --> Pageapi
目前功能只是完成了初步版本,待應用在項目中使用一段時間穩定以後將會開源~數組
Babel是一個工具鏈,主要用於編譯ECMAScript 2015+代碼轉換爲向後兼容的可運行在各類瀏覽器上的JavaScript。主要功能:瀏覽器
如上提供了babel基本的流程及一篇介紹AST的文章。
個人理解中好比一段string類型code,首先經過babel.transform會將code轉爲一個包含AST(Abstract Syntax Tree)的Object,一樣可使用@babel/generator將AST轉爲code完成逆向過程。 例如一段變量聲明代碼:
const a = 1;
複製代碼
在解析以後的結構爲:
{
"type": "Program",
"start": 0,
"end": 191,
"body": [
{
"type": "VariableDeclaration",
"start": 179,
"end": 191,
"declarations": [
{
"type": "VariableDeclarator",
"start": 185,
"end": 190,
"id": {
"type": "Identifier",
"start": 185,
"end": 186,
"name": "a"
},
"init": {
"type": "Literal",
"start": 189,
"end": 190,
"value": 1,
"raw": "1"
}
}
],
"kind": "const"
}
],
"sourceType": "module"
}
複製代碼
首先類型爲VariableDeclaration
,首先他的類型是const
,能夠經過點擊查看api其它還有let
、var
的值。其次是聲明declarations
部分,這裏值爲數組,由於咱們能夠同時定義多個變量。數組中值的類型爲VariableDeclarator
,包含id
和init
兩個參數,分別爲變量名稱以及變量值。id
的類型爲Identifier
,譯爲修飾符便是變量名稱。init
類型爲Literal
,便是常量,通常經常使用的有stringLiteral
、numericliteral
、booleanliteral
等。此時即完成了變量賦值的過程。
固然這只是很簡單的語法轉換,若是你們想學習更多關於轉換及類型的知識,可參考以下兩個官方連接:
首先定義目錄結構:
.
├── genCode // 代碼生成器
| ├── genDetail // 須要新頁面打開時單獨的detail目錄
| └── genIndex // 首頁
| └── genModels // umi models
| └── genServices // umi services
| └── genTableFilter // table篩選區域
| └── genTableForm // 非新頁面模式,新增/更新模態框
| └── genUpsert // 新頁面模式下,新增/更新頁面
| └── genUtils // 生成工具類
├── schema // 模型定義文件
| ├── table // 當前要生成的模型
| └── ├──config.js // 基礎配置
| └── └──dataSchema.js // 列表、新增、更新配置
| └── └──querySchema.js // 篩選項配置
├── scripts // 生成腳本
| ├── generateCode.js // 生成主文件
| └── index.js // 入口
| └── utils.js // 工具類
├── toCopyFiles // 生成時須要拷貝的文件,好比less
└── index.js // 主入口
複製代碼
主體流程爲:
eslint ${filePath} --fix
格式化生成的代碼。其中主要模塊爲genCode文件夾中根據json配置生成代碼的過程。 以genModels爲例,首先提取可使用template string
完成的部分,減小代碼解析的工做量。
module.exports = (tableConfig) => {
return `
import { message } from 'antd';
import { routerRedux } from 'dva/router'
import { parse } from 'qs'
${dynamicImport(dicArray, namespace)}
export default {
namespace: '${namespace}',
state: {
...
},
effects: {
*fetch({ payload }, { call, put }) {
const response = yield call(queryData, payload);
if (response && response.errorCode === 0) {
yield put({
type: 'save',
payload: response.data,
});
} else {
message.error(response && response.errorMessage || '請求失敗')
}
},
...,
${dynamicYieldFunction(dicArray)}
},
reducers: {
save(state, action) {
return {
...state,
data: action.payload,
};
},
...,
${dynamicReducerFunction(dicArray)}
},
};
`
}
複製代碼
由於列表數據可能有字典項從後臺獲取值來對應顯示,因此import
、effects
、reducers
模塊均有需根據配置動態生成的代碼。 以dynamecImport爲例:
function dynamicImport (dicArray, namespace) {
// 基礎api import
let baseImport = [
'queryData', 'removeData', 'addData', 'updateData', 'findById'
]
// 判斷json數據中是否有需從後臺加載項
if (dicArray && dicArray.length) {
baseImport = baseImport.concat(dicArray.map(key => getInjectVariableKey(key)))
}
// 遍歷生成依賴項
const _importDeclarationArray = map(specifier => (
_importDeclarationArray.push(t.importSpecifier(t.identifier(specifier), t.identifier(specifier)))
))
// 定義importDeclaration
const ast = t.importDeclaration(
_importDeclarationArray,
t.stringLiteral(`../services/${namespace}`)
)
// 經過@babel/generator 將ast生成code
const { code } = generate(ast)
return code
}
複製代碼
其它代碼生成邏輯相似,有不肯定如何生成的部分可參考上方提供的連接完成代碼轉換再去生成。
如有經過babel轉換沒法生成的代碼,可經過正則來完成。
例如如下umi-models
代碼:
*__dicData({ payload }, { call, put }) {
const response = yield call(__dicData, payload);
if (response && response.errorCode === 0) {
yield put({
type: 'updateDic',
payload: response.data,
});
} else {
message.error(response && response.errorMessage || '請求失敗')
}
}
複製代碼
基礎代碼可經過yieldExpression
生成,可是轉換以後無function以後的*
符號,反覆查了文檔以後沒有解決辦法,最後只能將生成完的code利用正則替換來解決。 若是你們有遇到相似的問題歡迎討論~
useEffect(() => {
props.form.setFieldsValue({
editorArea: BraftEditor.createEditorState(current.editorArea),
editorArea2: BraftEditor.createEditorState(current.editorArea2)
});
}, [current.editorArea, current.editorArea2]);
複製代碼
參數規範參考react-antd-admin 功能配置包含三個基礎配置文件:
config.json
配置基本屬性dataSchema.json
配置列表及新增修改字段querySchema.json
配置篩選區域字段參數 | 必填 | 類型 | 默認值 | 說明 |
---|---|---|---|---|
namespace | true | string | null | 命名空間 |
showExport | false | boolean | true | 是否顯示導出 |
showCreate | false | boolean | true | 是否顯示建立 |
showDetail | false | boolean | true | 是否顯示查看 |
showUpdate | false | boolean | true | 是否顯示修改 |
showDelete | false | boolean | true | 是否顯示刪除 |
newRouterMode | false | boolean | false | 在新的頁面新增/編輯/查看詳情。若包含富文本編輯器,建議此值設爲true,富文本在模態框展現不是很是美觀。 |
showBatchDelete | false | boolean | true | 是否顯示批量刪除,需multiSelection爲 true |
multiSelection | false | boolean | true | 是否支持多選 |
defaultDateFormat | false | string | 'YYYY-MM-DD' | 日期格式 |
upload | false | object | null | 上傳相關配置,上傳圖片和上傳普通文件分別配置。 詳見下方upload屬性 |
pagination | false | object | null | 分頁相關配置, 詳見下方pagination屬性 |
dictionary | false | array | null | 須要請求的字典項,用於下拉框或treeSelect的值爲從後端獲取的狀況,可在dataSchema 和querySchema中使用, 詳見下方dictionary屬性 |
參數 | 必填 | 類型 | 默認值 | 說明 |
---|---|---|---|---|
uploadUrl | false | string | null | 默認的上傳接口.優先級image/fileApiUrl > uploadUrl > Global.apiPath |
imageApiUrl | false | string | null | 默認的圖片上傳接口 |
fileApiUrl | false | string | null | 默認的文件上傳接口 |
image | false | string | '/uploadImage' | 默認的上傳圖片接口 |
imageSizeLimit | false | number | 1500 | 默認的圖片大小限制, 單位KB |
file | false | string | '/uploadFile' | 默認的上傳文件接口 |
fileSizeLimit | false | number | 10240 | 默認的文件大小限制, 單位KB |
參數 | 必填 | 類型 | 默認值 | 說明 |
---|---|---|---|---|
pageSize | false | number | 10 | 每頁顯示數量 |
showSizeChanger | false | boolean | false | 是否能夠改變pageSize |
pageSizeOptions | false | array | ['10', '20', '50', '100'] | 指定每頁能夠顯示多少條 |
showQuickJumper | false | boolean | false | 是否能夠快速跳轉至某頁 |
showTotal | false | boolean | true | 是否顯示總數 |
參數 | 必填 | 類型 | 默認值 | 說明 |
---|---|---|---|---|
key | true | string | null | 變量標識 |
url | true | string | null | 請求數據地址 |
參數 | 必填 | 類型 | 默認值 | 說明 |
---|---|---|---|---|
key | true | string | null | 惟一標識符 |
title | true | string | null | 顯示名稱 |
primary | false | boolean | false | 主鍵 若是不指定主鍵, 不能update/delete, 但能夠insert; 若是指定了主鍵, insert/update時不能填寫主鍵的值; |
showType | false | string | input | 顯示類型 input/textarea/inputNumber/datePicker/rangePicker/radio/select/checkbox/multiSelect/image/file/cascader/editor |
disabled | false | boolean | false | 表單中這一列是否禁止編輯 |
addonBefore | false | string/ReactNode | null | showType 爲input能夠設置前標籤 |
addonAfter | false | string/ReactNode | null | showType 爲input能夠設置後標籤 |
placeholder | false | string | null | 默認提示文字 |
format | false | string | null | 日期類型的格式 |
showInTable | false | boolean | true | 這一列是否要在table中展現 |
showInForm | false | boolean | true | 是否在新增或編輯的表單中顯示 |
validator | false | boolean | null | 設置校驗規則, 參考https://github.com/yiminghe/async-validator#rules |
width | false | string/number | null | 列寬度 |
options | false | array | null | format:[{ key: '', value: '' }]或string。showType爲cascader時,此字段暫不支持Array,數據只能經過異步獲取。 |
min | false | number | null | 數字輸入的最小值 |
max | false | number | null | 數字輸入的最大值 |
accept | false | string | null | 上傳文件格式限制 |
sizeLimit | false | number | 20480 | 上傳文件格式限制 |
url | false | string | null | 上傳圖片url。圖片的上傳接口, 能夠針對每一個上傳組件單獨配置, 若是不單獨配置就使用config.js中的默認值;若是這個url是http開頭的, 就直接使用這個接口; 不然會根據config.js中的配置判斷是否加上host |
sorter | false | boolean | false | 是否排序 |
actions | false | array | null | 操做 |
參數 | 必填 | 類型 | 默認值 | 說明 |
---|---|---|---|---|
keys | false | array | null | 容許更新哪些字段, 若是不設置keys, 就容許更全部字段 |
name | true | string | null | 展現標題 |
type | false | string | null | update/delete/newLine/component |
參數 | 必填 | 類型 | 默認值 | 說明 |
---|---|---|---|---|
key | true | string | null | 惟一標識符 |
title | true | string | null | 顯示名稱 |
placeholder | false | string | null | 提示語 |
showType | false | string | input | 顯示類型, 一些可枚舉的字段, 好比type, 能夠被顯示爲單選框或下拉框 input, 就是一個普通的輸入框, 這時能夠省略showType字段 目前可用的showType: input/inputNumber/datePicker/rangePicker/select/radio/checkbox/multiSelect/cascader |
addonBefore | false | string/ReactNode | null | showType 爲input能夠設置前標籤 |
addonAfter | false | string/ReactNode | null | showType 爲input能夠設置後標籤 |
defaultValue | false | string/array/number | null | 多選的defaultValue是個數組 |
min | false | number | null | showType爲 inputNumber 時可設置最小值 |
max | false | number | null | showType爲 inputNumber 時可設置最大值 |
options | false | array | null | options的key要求必須是string, 不然會有warning normal-format: [{"key": "", "value": ""}] cascader-format: [{"value": "", "label": "", children: ["value": "", "label": "", children: []]}] 若是值爲string,表明異步獲取的數據,則獲取當前命名空間下該key對應的值 |
defaultValueBegin | false | string | null | showType爲 rangePicker 時可設置默認開始值 |
defaultValueEnd | false | string | null | showType爲 rangePicker 時可設置默認結束值 |
placeholderBegin | false | string | 開始日期 | showType爲 rangePicker 時可設置默認開始提示語 |
placeholderEnd | false | string | 結束日期 | showType爲 rangePicker 時可設置默認結束提示語 |
format | false | string | null | 日期篩選格式 |
showInSimpleMode | false | boolean | false | 在簡單查詢方式下展現,若數據中有一項包含此字段且爲true的值,則開啓簡單/複雜篩選切換 |