寫在前面
馬上到了金三銀四的時間,很多公司開啟了今年第一輪招聘的熱潮,雖說今年是網際網路的寒冬,但是隻要對技術始終抱有熱情以及有過硬的實力,即使是寒冬也不會阻撓你前進的步伐。在面試的時候,往往在二面,三面的時面試官會結合你的簡歷問一些關於你簡歷上專案的問題,而以下這個問題在很多時候都會被問到
在這個專案中你有遇到什麼技術難點,你是怎麼解決的?
其實這個問題旨在瞭解你在遇到問題的時候的解決方法,畢竟現在前端技術領域廣,各種框架和元件庫層出不窮,而業務需求上有時紛繁複雜,觀察一個程式設計師在面對未知問題時是如何處理的,這個過程相對於只出一些面試題來考面試者更能瞭解面試者實際解決問題的能力
而很多人會說我的專案不大,並沒有什麼難點,或者說並不算難點,只能說是一些坑,只要google一下就能解決,實在不行請教我同事,這些問題並沒有困擾我很久。其實我也遇到過相同的情況,和麵試官說如何通過搜尋引擎解決這些坑的吧不太好,讓面試官認為你只是一個API Caller,但是又沒有什麼值得一談的專案難點
我的建議是,如果沒有什麼可以深聊的技術難點,不妨在日常開發過程中,試著封裝幾個常用的元件,同時嘗試分析專案的效能瓶頸,尋找一些優化的方案,同樣也能讓面試官對你有一個整體的瞭解
上篇分享了我在專案中是如何根據功能劃分模組以及效能優化的技巧,這章我會記錄設計和封裝元件的過程
技術棧是Vue + Element的單頁面應用
封裝元件
有人會說,github上那麼多好的開源元件,好的開源庫放著不用,為啥自己還要折騰呢?
其實我認為自己動手封裝一個元件還是很有意義的,因為如果是從零開始編寫的元件,你能夠更好的掌握自己元件的所有功能,並且還能根據公司的業務需求定製一些特殊的功能,除此之外,理解一個元件內部的實現機制也有助於提升個人的編碼能力,而不是別人問起來你只知道我用過某個元件,很好用,但是不知道是怎麼做到的。所以我還是比較推薦去嘗試編寫幾個常用的元件
因為是後臺管理系統,核心的元件肯定是表單元件和表格元件,公共元件是基於element元件的二次封裝,元件的設計遵循以下的思路
- 高內聚低耦合,儘可能少的暴露元件的api,將功能儘量封裝在元件內部
- 元件內部根據業務需求設定了一些元件預設的配置項,另外再通過不同頁面傳入不同配置項提高元件的通用性
設計元件的目的就是讓元件進一步解耦,將配置項和模板標籤分離,一方面是減少在業務邏輯元件中的程式碼量,另一方面就是單獨抽離的配置項使得能夠通過後臺動態傳遞給前端,或者自己建一個配置項的js/ts檔案(如果有規範的開發者文件還可以使用nodejs編寫一個讀取開發者文件一鍵寫入配置項的指令碼,進一步提升開發效率)
表格元件
表格元件設計大致分為以下幾個部分
- 儘量貼近element元件庫的api
- 傳入一個表頭的配置項陣列,通過這個配置項動態生成el-table-columns標籤
- 互動複雜的表頭列的解決方式
儘量貼近element元件庫的api
元件中使用了$attrs,$listeners實現屬性和監聽事件的跨級傳遞,使得在頁面中給自定義元件中的傳入的屬效能夠通過自定義元件內部的轉發直接成為el-table標籤的屬性,達到跨級的屬性傳遞,而$listeners和$attrs類似,能夠監聽el-table元件中觸發的事件,將事件轉發 到頁面中的自定義元件上
自定義元件:
這樣做的目的就是能讓你的自定義元件和el-table元件有相同的用法,傳入的屬性,監聽的事件也是相同的
在頁面中使用自定義元件,可以看到z-table和el-table的用法幾乎是相同的,只需要額外傳入一個columns的屬性:
表頭的配置項設計
繼續給這個表格元件新增表頭標籤,這裡我把一些不必要的判斷都去除了,只留下了核心的邏輯,實際在元件內部只需要迴圈這個配置項動態生成el-table-column標籤就可以了
自定義元件:
配置項檔案:
這裡的核心是在於這個v-bind
,當v-bind後面等號裡放入的是一個物件時,它會遍歷這個物件的所有屬性,將屬性和值一一做繫結
什麼意思呢?這裡結合配置項檔案來說明,如果傳入上述的配置項,元件的內部實際是這樣子的
拋開key不談,在配置項的每個元素中暴露一個attrs屬性,裡面儲存了所有el-table-column標籤可以接受的屬性。例子中label,prop,width這3個屬性是在配置項每個元素的attrs屬性中的,通過v-bind="column.attrs"讓這3個屬性和它們的值分別在el-table-column標籤中做了繫結,從而達到了模板和配置項解耦的目的
互動複雜的表頭列的解決方式
對於一些需要特別處理的表頭列的資料,我在元件內部利用插槽和作用域插槽,通過插槽定義表頭列的插入位置,再通過作用域插槽將資訊返回給父元件,在父元件中定義如何顯示,使得表格元件非常的靈活能夠應對大部分業務需求
可以看到具名插槽的名字也是通過配置項傳入的,並且作用域插槽將整個表單內部的資料通過scope傳給父元件,在複雜的業務場景,無法通過配置項解決問題的時候,通過插槽和作用域插槽讓父元件去決定如何去處理資料
配置項中新增插槽屬性:
頁面元件:
在頁面元件中,可以和element提供的作用域插槽的使用方式相似,通過scope可以訪問到元件內部的所有資料並且交給頁面元件去做複雜的邏輯處理
其他功能
針對公司的需求,我對元件做了進一步的改造
- 使用render函式使得表頭顯示能夠更加靈活
- 配置項暴露一個函式能夠讓當前列的資料執行這個函式達到預處理的效果
- 配置項中設定一個二維陣列,能夠讓資料欄位組合,達到資料顯示在不同的行數的效果
- 新增了操作圖示
- 新增了資料(code碼)轉對應中文語義的功能
原始碼
表單元件
表單元件相對於表格元件在實現方面要困難一點,因為表單的控制元件非常多,每個配置項又需要非常靈活,這裡我借鑑了之前在知乎看到的一篇部落格,文章中雖然沒有把程式碼列出來,但是羅列了整體的實現方案,隨後我根據文章中的思路設計了這個表單元件
設計大致分為以下幾個部分
- 表單配置項設計
- 表單驗證
- 表單請求
- 表單控制元件之間的聯動
- 呼叫後端介面生成表單控制元件的選項
表單配置項設計
根據上面的表格元件的封裝思路,還是利用$attrs做根元素屬性的傳遞,用v-bind在配置項中設定元件內部的屬性
表單元件:
配置項檔案:
和表格元件不同的是,因為表單元件分為el-form-item標籤和表單控制元件2部分,這2個部分都需要在配置項中對應配置屬性,在配置項中使用itemAttrs控制el-form-item標籤的屬性,使用attrs控制表單控制元件的屬性
這裡還用到了component標籤,通過配置項的tag標籤動態生成el-input的表單控制元件,但是可以看到這裡我並沒有直接將tag的值設為el-input,那input是如何變成el-input的呢?
這裡我又定義了每個元件通用的配置項,使得不需要每次都在元件的attrs中宣告一些重複的屬性,比如placeholder,clearable等
通用配置項檔案:
最重要的是我建立了元件配置項和通用配置項之間的關聯,通過元件配置項中的tag屬性找到通用配置項對應的物件,結合上面的例子如果tag的值是input,那就會從通用配置項中找到input屬性對應的物件,並且將真實的tag值指向通用配置項的component,這裡就是el-input
而這種關聯又是怎麼建立起來的呢,其實還是用了Object.assgin做了物件之間的合併
核心邏輯如下(引數formItem指的是元件配置項formItems中的每個元素):
這裡定義了一個computeFormItem的函式,通過傳入配置項陣列的每個元素,根據元素的tag值找到通用配置項(basic物件)中相應的值,隨後用了Object.assgin做了合併,關於這個computeFormItem函式我稍後在後面的表單控制元件之間的聯動中會詳細去講
通用和元件配置項都有了,接下來要實現的是表單元件需要上傳給後端的資料物件
這裡我的思路是通過配置項中宣告的欄位名(key)動態生成資料物件,這樣可以減少傳入的配置項的數量,在元件內部宣告Model變數儲存資料物件
但是這裡有2點需要注意
- 因為元件內部宣告的Model是一個空物件,Vue的響應式系統是監聽不到物件建立了新的屬性,需要使用$set來設定,使得能夠強制更新檢視
- 這裡需要通過formItems對Model資料物件賦值,如果放在mounted鉤子中執行的話拿到的是一個空陣列,所以我這裡使用watch來監聽formItems,並且使用immediate立即執行(用computed宣告一個新陣列理論上也可以)
表單驗證
表單驗證方面儘量貼合element元件的傳入方式,保持所有在el-form-item標籤中寫的屬性都寫在itemAttrs中,所有在表單控制元件中寫的屬性都寫在attrs中,所以可以在itemAttrs中編寫表單驗證方面的邏輯
表單請求
表單請求方面,因為在重構時新建了api資料夾,存放的是一個個後端介面的api函式,做到一個頁面對應一個api資料夾中的一個介面檔案
每個介面檔案中可以匯出多個介面的函式
在表單元件中只需要宣告一個api的props讓頁面元件傳入就可以了
隨後給提交按鈕繫結click事件,進行表單驗證最後執行介面函式,傳入Model這個資料物件即可
在介面函式呼叫成功返回響應資料後,我這裡通過觸發after-submit的事件讓頁面元件監聽這個事件,並且把響應資料傳給頁面元件,這樣頁面元件就能拿到響應的資料並且做一些處理了
頁面元件監聽after-submit事件:
表單控制元件之間的聯動
這一部分我認為也是最難實現的,在日常的業務需求中可能需要某個控制元件控制另外一個控制元件顯示與否
核心的思路就是在配置項中定義一個getAttrs的函式,這個函式根據當前Model,也就是資料物件中的某個值動態的生成一個attrs物件,最後將這個attrs物件通過合併到當前配置項的attrs中,另外還定義了一個ifRender函式,可以控制表單控制元件是否被渲染,最後我們的配置項可能長這樣
接下來表單元件內部要實現如何執行這2個函式,依舊是之前的computeFormItem這個函式,它用來計算出當前表單元件的配置項
和上面的computeFormItem函式不同的是,我這裡傳入了第二個引數Model,使得當前的表單元件配置項能夠根據Model動態的變化,在內部執行getAttrs函式傳入這個Model,返回的物件通過Object,assgin合併到當前的配置項中,而對於另一個ifRender函式,也傳入Model,返回一個Boolean值,最後用這個Boolean值在模版中通過v-if控制是否渲染表單控制元件
這裡要分析一下整個表單最核心的部分:computeFormItem函式,它的作用是根據當前Model中的資料變化,動態的生成一個新的配置項,因為我們的表單控制元件是根據配置項對映而成的,需要改變表單控制元件只能去修改配置項
根據上面那個圖可以發現,我們並沒有直接使用頁面元件傳來的formItems配置項,而是根據_formItems迴圈渲染的標籤,而_formItems是基於formItems並且經過computeFormItem生成的配置項,只要Model中的資料改變,這個配置項就需要重新計算生成新的值,所以我選擇把_formItems放在計算屬性中
這樣,只要依賴項(這裡是Model和formItems)變了,就會觸發函式重新計算出新的_formItems
下拉框/單選框/核取方塊
在表單元件中,我使用component標籤動態生成表單控制元件,但是對於一些有子節點的表單控制元件通過component實現就有些困難,這裡我將含有子節點的元件(下拉框/單選框/核取方塊)又進行了一層封裝,消除了子節點,讓所有屬性都在component這一層配置
自定義select元件
這樣以來只要在配置項中宣告一個options屬性,通過component標籤將元件轉為自定義的select元件,讓options屬性就會變為select元件的屬性,這樣在自定義的select元件內部可以通過$attrs.options獲取到它(這裡注意value,label必須都要顯式的宣告否則會報錯,因為element元件內部會對傳入的屬性驗證)
元件配置項檔案:
這裡再次利用通用配置項檔案,將元件配置項中宣告的select元件的配置項對映到自定義的select元件中
呼叫後端介面生成表單控制元件的選項
在真實的業務需求中,部分下拉框,單選框的選項是通過拉取後端的介面生成的。放在表單元件中的話還是需要修改配置項,在頁面元件中修改formItem。找到下拉框/單選框的key,將介面的資料賦值給options屬性
總結
可以看到表單元件還是比較複雜的,其實這個表單元件相對於表格元件來說還是有一定的侷限性,後續可能會給它設計插槽的功能。另外真實的業務需求肯定是更加複雜多變的,不管怎麼說,一些互動邏輯不是特別複雜的表單這個元件還是能hold住的,本人能力有限,這裡也只是給一個思路,希望後續能夠愈發完善
原始碼
一鍵生成配置項
介紹一款我自己寫的工具庫,可以和表格元件完美配合,讀取開發者文件,一鍵生成元件的配置項,免除多欄位輸入的錯誤和重複勞動,有幫助的話希望各位賞個 star ~