摘要:配置涵蓋了目前的業務場景的基本需求,但是可擴展性很低。最終決定采用的生態鏈來解決上述遇到的問題。在指定的路徑下寫入對應的文件。
大綱
遇到的問題場景及解決方案對比
什么是babel?
解決過程
目前遺留的問題
目前實現功能API
參考
遇到的問題場景及解決方案對比我們目前采用的是antd + react(umi)的框架做業務開發。在業務開發過程中會有較多頻繁出現并且相似度很高的場景,比如基于一個table的基礎的增刪改查,這個相信大家都非常熟悉。在接到一個新的業務需求的時候,相信有不少人會選擇copy一份功能類似的代碼然后基于這份代碼去改造以滿足當前業務,當然我目前也是這樣做的~
其實想把這塊功能提取成一個公共組建的想法由來已久,最近開始做基礎組件,便拿這個下手了。經過一周左右的時間完成了基礎組件的編寫。
查看基礎支持的功能點API。
基本的思路是通過json生成一些抽象配置,然后通過解析json的抽象配置+渲染器最終生成頁面。json配置涵蓋了目前80%的業務場景的基本需求,但是可擴展性很低。比如一些復雜的業務場景:表單的關聯校驗、數據關聯顯示、多級列表下鉆等等功能。雖然通過一些較為復雜的處理可以把這些功能融入進來,但最終組件將會異常龐大難以維護。
所以,我能不能通過這些json配置通過某種工具生成對應的代碼?這樣一來以上提到的問題就完全不存在了,因為這和我們自己寫的代碼完全一樣,工具只是幫我們完成初始化的過程。所以后來想了很多辦法,最初采用template string的方式,這種方式較為簡單粗暴,無非通過string中嵌套變量的判斷來輸出code。但是在實際寫的時候發現很多問題,比如
function的輸出(JSON.stringify會將function忽略)
多層函數嵌套之后怎么獲取最終渲染的節點code
嵌入變量怎么實現、umi-models-effects/reducer中額外的字典查詢怎么生成等等..
最終學習了一些生成代碼的工具比如angular-cli以及一些關于js生成代碼的文章,主要是通過知乎上的這篇討論了解到了大家是怎么處理這種問題的。最終決定采用babel的生態鏈來解決上述遇到的問題。
我們目前采用的方式是基于antd+react(umi)編寫通用的CRUD模板,然后通過代碼生成器解析json中的配置生成對應的代碼,大致的流程是:
React --> JavaScript AST ---> Code Generator --> Compiler --> Page
目前功能只是完成了初步版本,待應用在項目中使用一段時間穩定之后將會開源~什么是babel?
Babel是一個工具鏈,主要用于編譯ECMAScript 2015+代碼轉換為向后兼容的可運行在各種瀏覽器上的JavaScript。主要功能:
語法轉換
環境中缺少的Polyfill功能
源代碼轉換
查看更多Babel功能
Understanding ASTs by Building Your Own Babel Plugin
如上提供了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等。此時即完成了變量賦值的過程。
當然這只是很簡單的語法轉換,如果大家想學習更多關于轉換及類型的知識,可參考如下兩個官方鏈接:
babel-types
ast轉換工具
解決過程首先定義目錄結構:
. ├── 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 // 主入口
主體流程為:
指定要生成代碼的路徑。
根據schema中當前json配置路徑,依次調用genCode目錄中各個模塊的代碼生成方法獲取對應code。
在指定的路徑下寫入對應的文件。
執行eslint ${filePath} --fix格式化生成的代碼。
根據配置對應復制toCopyFiles文件夾中依賴的less等文件到對應的文件夾。
其中主要模塊為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利用正則替換來解決。
如果大家有遇到類似的問題歡迎討論~
目前使用的編輯器組件為braft-editor,但是結合antd使用initialValue不生效,必須使用setFieldsValue。但是使用useEffects時會默認添加props.form作為依賴并且props.form會不斷變化而觸發死循環,目前無奈只有禁用eslint react-hooks/exhaustive-deps。
useEffect(() => { props.form.setFieldsValue({ editorArea: BraftEditor.createEditorState(current.editorArea), editorArea2: BraftEditor.createEditorState(current.editorArea2) }); }, [current.editorArea, current.editorArea2]);
生成的代碼怎么刪除未使用的依賴?使用eslint --fix不會刪除未使用的變量定義。
初始化之后的代碼要修改怎么辦?因當前方法只會完成代碼初始化過程,以后修改的過程暫無思路解決。
功能API參數規范參考react-antd-admin
功能配置包含三個基礎配置文件:
config.json配置基本屬性
dataSchema.json配置列表及新增修改字段
querySchema.json配置篩選區域字段
config.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/a... |
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的值,則開啟簡單/復雜篩選切換 |
react-antd-admin
AST語法轉換器
babel-types-api
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109678.html
摘要:經過一周左右的時間完成了基礎組件的編寫。配置涵蓋了目前的業務場景的基本需求,但是可擴展性很低。最終決定采用的生態鏈來解決上述遇到的問題。在指定的路徑下寫入對應的文件。大綱 遇到的問題場景及解決方案對比 什么是babel? 解決過程 目前遺留的問題 目前實現功能API 參考 遇到的問題場景及解決方案對比 我們目前采用的是antd + react(umi)的框架做業務開發。在業務開發過程中會...
摘要:前端基礎架構和硬核介紹技術棧的選擇首先我們構建前端架構需要對前端生態圈有一切了解,并且最好帶有一定的技術前瞻性,好的技術架構可能日后會方便的擴展,減少重構的次數,即使重構也不需要大動干戈,我通常選型技術棧會參考以下三點一提出自身業務的需求是 # 前端基礎架構和硬核介紹 showImg(https://segmentfault.com/img/remote/146000001626972...
摘要:前端基礎架構和硬核介紹技術棧的選擇首先我們構建前端架構需要對前端生態圈有一切了解,并且最好帶有一定的技術前瞻性,好的技術架構可能日后會方便的擴展,減少重構的次數,即使重構也不需要大動干戈,我通常選型技術棧會參考以下三點一提出自身業務的需求是 # 前端基礎架構和硬核介紹 showImg(https://segmentfault.com/img/remote/146000001626972...
摘要:前端準備前端了解過關了嗎前端基礎架構和硬核介紹技術棧的選擇首先我們構建前端架構需要對前端生態圈有一切了解,并且最好帶有一定的技術前瞻性,好的技術架構可能日后會方便的擴展,減少重構的次數,即使重構也不需要大動干戈,我通常選型技術棧會參考以下三 # 前端準備 :前端了解過關了嗎?前端基礎架構和硬核介紹 showImg(https://segmentfault.com/img/remote/...
閱讀 2554·2021-09-30 10:00
閱讀 3490·2021-09-22 10:54
閱讀 6212·2021-09-07 10:28
閱讀 2943·2019-08-29 13:53
閱讀 741·2019-08-29 12:42
閱讀 958·2019-08-26 13:51
閱讀 1257·2019-08-26 13:32
閱讀 3020·2019-08-26 10:39