天天看點

前端元程式設計——使用注解加速你的前端開發

無論你用 React,Vue,還是 Angular,你還是要一遍一遍寫相似的 CRUD 頁面,一遍一遍,一遍一遍,一遍又一遍……

“天下苦秦久矣”~~

前端元程式設計——使用注解加速你的前端開發

現在的前端開發,我們有了世界一流的 UI 庫 React,Vue,Angular,有了樣式豐富的 UI 元件庫 Tea (騰訊雲 UI 元件庫,類似 Antd Design), 有了友善強大的腳手架工具(例如,create react app)。但是我們在真正業務代碼之前,通常還免不了寫大量的樣闆代碼。

現在的 CRUD 頁面代碼通常:

太輕的“Model”或着“Service”,大多時候隻是一些 API 調用的封裝。

胖”View“,View 頁面中有展示 UI 邏輯,生命周期邏輯,CRUD 的串聯邏輯,然後還要塞滿業務邏輯代碼。

不同的項目業務邏輯不同,但是清單頁,表單,搜尋這三闆斧的樣闆代碼,卻要一遍一遍占據着前端工程師的寶貴時間。

特别是 CRUD 類應用的樣闆代碼受限于團隊風格,後端 API 風格,業務形态等,通常内在邏輯相似書寫上卻略有差別,無法通過一個通用的庫或者架構來解決(上圖中背景越深,越不容易有一個通用的方案)。

說好的“資料驅動的前端開發”呢?

對于這個“痛點”——怎麼盡可能的少寫模版代碼,就是本文嘗試解決的問題。

我們嘗試使用 JavaScript 新特性​<code>​Decorator​</code>​和​<code>​Reflect​</code>​元程式設計來解決這個問題。

從 ECMAScript 2015 開始,JavaScript 獲得了 ​<code>​Proxy​</code>​ 和 ​<code>​Reflect​</code>​ 對象的支援,允許你攔截并定義基本語言操作的自定義行為(例如,屬性查找,指派,枚舉,函數調用等)。借助這兩個對象,你可以在 JavaScript 元級别進行程式設計。MDN

在正式開始之前,我們先複習下​<code>​Decorator​</code>​和​<code>​Reflect​</code>​。

這裡我們簡單介紹 Typescript 的​<code>​Decorator​</code>​,ECMAScript 中​<code>​Decorator​</code>​尚未定稿,但是不影響我們日常的業務開發(Angular 同學就在使用 Typescript 的​<code>​Decorator​</code>​)。

簡單來說,​<code>​Decorator​</code>​是可以标注修改類及其成員的新語言特性,使用​<code>​@expression​</code>​的形式,可以附加到,類、方法、通路符、屬性、參數上。

TypeScript 中需要在​<code>​tsconfig.json​</code>​中增加​<code>​experimentalDecorators​</code>​來支援:

比如可以使用類修飾器來為類擴充方法。

Reflect 是 ES6 中就有的特性,大家可能對它稍微陌生,Vue3 中依賴 Reflect 和 Proxy 來重寫它的響應式邏輯。

簡單來說,​<code>​Reflect​</code>​是一個人内置的對象,提供了攔截 JavaScript 操作的方法。

Reflect Metadata 是 ES7 的一個提案,Typescript 1.5+就有了支援。要使用需要:

​<code>​npm i reflect-metadata --save​</code>​

在 ​<code>​`tsconfig.json`​</code>​ 裡配置 ​<code>​`emitDecoratorMetadata`​</code>​ 選項

簡單來說,Reflect Metadata 能夠為對象添加和讀取中繼資料。

如下可以使用内置的​<code>​design:key​</code>​拿到屬性類型:

回到正題——使用 Decorator 和 Reflect 來減少 CRUD 應用中的樣闆代碼。

前端元程式設計——使用注解加速你的前端開發

CRUD 頁面無需多言,清單頁展示,表單頁修改 ……包括 API 調用, 都是圍繞某個資料結構(圖中​<code>​Person​</code>​)展開,增、删、改、查。

基本思路很簡單,就像上圖,Model 是中心,我們就是借助​<code>​Decorator​</code>​和​<code>​Reflect​</code>​将 CRUD 頁面所需的樣闆類方法屬性元程式設計在 Model 上。進一步延伸資料驅動 UI的思路。

前端元程式設計——使用注解加速你的前端開發

借助 Reflect Matadata 綁定 CRUD 頁面資訊到 Model 的屬性上

借助 Decorator 增強 Model,生成 CRUD 所需的夜班代碼

下文,我們用TypeScript和React為例,元件庫使用騰訊​​Tea component​​ 解說這個方案。

首先我們有一個函數來生成不同業務的屬性裝飾函數。

一個類裝飾器,處理通過資料裝飾器收集上來的中繼資料。

TypeScript 項目中第一步自然是将後端資料安全地轉換為​<code>​type​</code>​,​<code>​interface​</code>​或者​<code>​Class​</code>​,這裡 Class 能在編譯後在 JavaScript 存在,我們選用​<code>​Class​</code>​。

重點在​<code>​handle?: string | ServerHandle​</code>​函數,在這個函數處理 API 資料和前端資料的轉換,然後在​<code>​constructor​</code>​中集中處理。

清單頁中一般使用 Table 元件,無論是 Tea Component 還是 Antd Design Component 中,樣闆代碼自然就是寫那一大堆 Colum 配置了,配置哪些 key 要展示,表頭是什麼,資料轉化為顯示資料……

首先我們收集 Tea Table 所需的​<code>​TableColumn​</code>​類型的 column 中繼資料。

然後在 EnhancedClass 中收集,生成 column 清單。

自然我們封裝一個更簡易的 Table 元件。

​<code>​getConfigMap&lt;T&gt;(F: any, cachekey: symbol,metaKey: symbol): Map&lt;string,T&gt;​</code>​ 收集中繼資料到 Map

​<code>​static getColumns&lt;T&gt;(): EnhancedTableColumn&lt;T&gt;[]​</code>​ 得到 table 可用 column 資訊。

效果很明顯,不是嗎? 7 行寫一個 table page。

表單,自然就是字段的 name,label,require,validate,以及送出資料的轉換。

Form 表單我們使用Formik + Tea Form Component + yup(資料校驗)。Formik 使用 React Context 來提供表單控件所需的各種方法資料,然後借助提供的 Field 等元件,你可以很友善的封裝你的業務表單元件。

照貓畫虎,我們還是先收集 form 所需的中繼資料

有了中繼資料,我們可以在 EnhancedClass 中生成 form 所需:

initialValues

資料校驗的 validationSchema

各個表單元件所需的,name,label,required 等

送出表單的資料轉換 handle 函數

上文包含了不少的代碼,但是大部頭在如何将中繼資料轉換成為頁面元件可用的資料,也就是元程式設計的部分。

而業務頁面,7 行的 Table 頁面,40 行的 Form 頁面,已經非常精簡功能完備了。根據筆者實際項目中估計,可以節省至少 40%的代碼量。

寫到尾聲,你大概會想到某些配置系統,前端 CRUD 這個從古就有的需求,自然早就有方案,用的最多的就是配置系統,在這裡不會過多讨論。

簡單來說,就是一個單獨的系統,配置類似上文的元資訊,然後使用固定模版生成代碼。

思路實際上和本文的元程式設計類似,隻是元程式設計成本低,你不需要單獨做一個系統,更加輕量靈活,元程式設計代碼在運作時,想象空間更大……

上面隻是 table,form 頁面的代碼展示,由此我們可以引申到很多類似的地方,甚至 API 的調用代碼都可以在元程式設計中處理。

前端元程式設計——使用注解加速你的前端開發

元程式設計——将中繼資料轉換成為頁面元件可用的資料,這部分恰恰可以在團隊内非常好共享也需要共同維護的部分,帶來的好處也很明顯:

最大的好處自然就是生産效率的提高了,而且是低成本的實作效率的提升(相比配置系統)。一些簡單單純的 CURD 頁面甚至都不用寫代碼了。

更易維護的代碼:

“瘦 View“,專注業務,

更純粹的 Model,你可以和 redux,mobx 配合,甚至,你可以從 React,換成 Angular)

最後更重要的是,元程式設計是一個低成本,靈活,漸進的方案。它是一個運作時的方案,你不需要一步到羅馬,徐徐圖之 …… - ……

前端元程式設計——使用注解加速你的前端開發

前端元程式設計,較少你的樣闆代碼,加速前端開發