1. 程式人生 > >從0到1釋出一個Vue Collapse元件

從0到1釋出一個Vue Collapse元件

需求背景

最近在專案中遇到了一個類似Collapse的互動需求,因此到github上找了一圈關於Vue Collapse的相關輪子,但是多少都有些問題。有的是實現問題,例如vue2-collapse,伸縮部分採用max-height指定動畫,存在缺陷;還有的是擴充套件性問題,遇到定製場景比較棘手。因此,決定自己擼一個Collapse元件。從專案中的一個需求,到目前已將它開源併發布到npm,還是踩了許多坑的。程式碼雖然簡單,但是過程卻不太容易。因此這篇文章不是安利這款元件r-collapse-vue,僅僅是想記錄一下整個開發生命週期,需要做什麼,以及遇到什麼問題。當然了,如果這個元件或是這篇文章對你有幫助,勞煩點進去給個star,萬分感謝~

開發流程

我們的整個開發流程,可以簡單的總結如下:

  1. 專案腳手架搭建(Vue CLI3)
  2. 元件功能開發
  3. 單元測試(Vue Test Utils + Jest)
  4. 文件編寫(Vue Styleguidist + Github Pages)
  5. 釋出NPM
  6. 持續整合配置(TravisCI)

我們來詳細聊一聊每個過程是如何實施的,且遇到了哪些問題。

腳手架搭建

腳手架我們直接使用Vue CLI來搭建即可,其已經提供了豐富的功能,並且可以通過vue.config.js擴充套件webpack的能力。但是要注意的是,我們的構建產物是一個模組,而不是我們平時在專案中構建出一個應用。我們希望構建出來的模組是一個相容CommonJs或是UMD,以便於使用者在不同的環境中引用。所幸,Vue CLI3也給我提供了這樣一個功能,詳細可參考文件。

其次,本次開發我選擇了TypeScript,腳手架預設集成了vue-property-decorator。使用之後直觀的感受就是,Vue的整個生態對TS的支援還不夠完善,但整體還是比較爽的,期待官方在3.0中能夠徹底支援TS。本文主題不是討論TS,因此簡單羅列下使用時遇到的問題:

  • 在template中無法做到智慧提示,需要智慧提示只能使用tsx,這一點是比較痛苦的
  • 定義Prop時需要加非空斷言(!:),否則會報錯,例如:
@Prop({ required: true })
public value!: String;
  • 使用Vue Test Utils寫單測時,無法對自定義的Vue元件進行型別推導,見下文
  • 使用Vue Styleguidist編寫文件demo不支援TS,見下文

元件功能開發

在日常寫業務的時候,我們可能會在元件當中耦合很多的業務邏輯。但是作為一個通用元件,我們在開發的時候要儘可能保證它的擴充套件性,因此我們希望達到的一個目標就是:在保證開發體驗的前提下提高擴充套件性。對於Collapse元件,UI方面一般都是按照各自的設計稿來自行編寫的,因此我們只需要提供功能即可。更好的方式是提供預設的UI,但又可以支援完全定製,這個是目前r-collapse-vue可以完善的一個點。
在進行功能設計的過程中,我們要先確定我們需要支援哪些功能,以r-collapse-vue舉例,需要提供的功能包括:

  • 基本的展開/收縮(支援動畫)
  • 手風琴模式
  • 自定義點選事件
  • Collapse巢狀

在實現的過程中,我們也需要思考很多細節,舉幾個例子:

  1. 使用者如何控制每一個Collapse的狀態?

最簡單的想法是傳遞一個類似叫做status的prop,在每一個Collapse內部去維護這個狀態。但是這樣會有一個問題,我們如何去支援手風琴模式,即一個展開另外的都需要收起。按照這種做法,需要用一個父元件包裹,去獲取每一個Collapse子元件的例項,呼叫例項方法去控制。這樣做不是不行,vue2-collapse就是這麼做的,但是我認為不夠優雅。因此我們重新整理思路,每一個Collapse之間的狀態可能會互相影響,我們常用的解決方法是狀態提升,因此我的做法是抽象兩個元件,Collapse和CollapsePanel,Collapse即是父元件,提供狀態控制,將狀態傳遞給其內部巢狀的CollapsePanel,在內部消化掉所有的邏輯,這更加符合單向資料流的思想,站在使用者角度來看,寫法也能夠相對統一,使用時我們只需這麼寫:

<r-collapse v-model="activeKeys">
    <r-collapse-panel name="a">xxxx</r-collapse-panel>
    <r-collapse-panel name="b">xxxx</r-collapse-panel>
</r-collapse>
  1. 實際場景中經常會對展開和收縮排行樣式區分,如何幫助使用者提升開發體驗?

見上面的程式碼,我們在CollapsePanel中傳入了一個name屬性作為唯一標識,此時使用者可結合activeKeys自行判斷當前panel是否展開:

<r-collapse v-model="activeKeys">
    <r-collapse-panel
        name="a"
        :class="activeKeys.includes('a') ? 'active': ''"
    >
        xxxx
    </r-collapse-panel>
    <r-collapse-panel name="b">xxxx</r-collapse-panel>
</r-collapse>

這種方法雖然可以,但是存在兩個問題:

  • 使用者需要自行新增邏輯,體驗不夠友好
  • 每次重新渲染都會執行額外的邏輯判斷,效能不夠友好

因此,可以提供一個activeClass的prop,讓使用者可以自定義展開狀態的類名,就可以避免以上的問題。

這些細節問題看似簡單,但是作為一個通用元件的開發者,我們應該經常站在使用者的角度看問題,才能不斷地提升元件的開發體驗。

單元測試

一個優秀的開源元件一定少不了單元測試,例如Ant Design等開源庫都有著很高的單測覆蓋率。一開始寫單測可能會覺得耗時、沒有必要,但其實單測能夠帶來諸多的好處:

  1. 單測相較手動測試,能夠減少bug率,覆蓋的場景更全,且測試較為方便
  2. 開源的元件可能會有很多的維護者,單測能夠降低模組之間互相影響產生bug的概率
  3. 使用者一般都會選擇單測覆蓋率較高的輪子

因此,單測必不可少,目前前端常見的選擇包括:

  • Jest,FaceBook出品,配置簡單,使用JSDOM模擬測試環境,當遇到操作真實DOM的場景,如獲取scrollHeight等比較乏力
  • Karma + Mocha,Mocha同Jest都是測試框架,而Karma為框架提供了真實的瀏覽器測試環境,如果程式碼中對DOM操作較多,建議使用這種組合。但是Mocha配置較複雜,且需要自行安裝斷言庫

Vue當中已經給我們提供了單測相關的工具Vue Test Utils,它提供了很多功能,如元件掛載,獲取例項等等,使用它配合Jest或者Mocha能夠比較方便的完成單測,詳情參考文件。

在編寫單測時,我們需要注意,對於UI元件來說,不應一味追求行級覆蓋率,應當只關注輸入輸出,避免涉及過多的實現細節,從而避免瑣碎的測試。例如,我們測試展開功能,只需要觸發click,檢測status是否為true即可,無需關注過程中是觸發了xxx事件還是發生了其他事情,這樣當我們的邏輯修改後能夠保證單測還能有效。同時,在用TS編寫單測時,通過Vue Test Utils建立的wrapper是普通的Vue型別,因此自定義的Vue元件無法進行型別推導,此時要獲取例項屬性時需要通過(wrapper.vm as any).xxx來獲取。經查閱資料,官方表示目前沒法解決這個問題,只能使用這種方式。

文件編寫

一個好的文件能夠方便使用者明白你的設計理念,因此我們想要的文件不僅需要有完整的API描述,並且在展示demo時能夠同時展示原始碼,類似於在Ant Design或Element中那樣。我們這邊使用的是Vue Styleguidist。

它通過vue-docgen-api,能夠將註釋轉換成屬性描述展現在頁面上。因此我們只需要寫註釋,就能夠生成元件屬性相關的文件。而我們的另一個需求,在展示demo時能夠同時展示原始碼,它也能夠做到。我們可以通過兩種方式:

  • 在Vue元件中使用<docs></docs>標籤來寫demo,這樣做對元件有侵入,感覺不太好
  • 新建一個markdown檔案,內部通過特殊的標記寫入vue程式碼即可

我們選用第二種方式,但是又遇到了許多坑。比如寫入md的Vue程式碼不支援TS,試了很多的方法都沒有解決,後來還是改成了JS寫法;還有SCSS使用巢狀時,巢狀的內容未被正確解析,後改成了CSS。其實這個東西的實現難度並不高,在md中寫Vue無非就是寫個webpack外掛解析.md格式的檔案,取出Vue的部分通過vue-loader處理,鑑於bug這麼多且樣式我認為不夠美觀,之後有時間可以再造個輪子玩一玩。

在Vue CLI3中使用Vue Styleguidist十分方便,只要執行:

vue add styleguidist

然後在package.json的scripts中新增:

"serve:doc": "vue-cli-service styleguidist",
"build:doc": "vue-cli-service styleguidist:build"

就可以拆箱即用了。

文件編寫完成,我們執行yarn build:doc構建文件,發現輸出的是一個html檔案,此時我們可以選擇使用Github Pages來作為我們的靜態資源伺服器展示文件,因為它方便部署且免費。過程如下:

  1. 將styleguide.config.js中的styleguideDir選項改為"docs",即將build的目標目錄設定為docs
  2. 在Github對應倉庫的settings中將GitHub Pages的Source選項設定為master branch/docs folder,意味著會自動從倉庫的docs目錄獲取靜態資源

這樣每次更新docs會自動部署更新文件。

說完文件,我們還需要編寫在Github上展示的README,這裡推薦一個生成README的庫,readme-md-generator,格式非常簡潔且美觀。在README中,我們可以新增如下的小圖示:

這個可以使用shields生成,它能關聯你的NPM、Github等等,實時更新icon資訊,有了它文件逼格瞬間高多了。

釋出NPM

要將包釋出到NPM,我們需要做如下的準備工作:

  1. 到https://www.npmjs.com/上註冊一個NPM的賬號
  2. 本地執行
npm login --registry=https://registry.npmjs.org

注意,這邊加上registry為了防止在全域性或當前環境覆寫.npmrc,導致登入的不是NPM源。

  1. 修改package.json的配置,可以參考v-collapse-vue的部分配置:
{
  "name": "r-collapse-vue",
  "version": "1.0.0",
  "description": "a collapse component for VueJs",
  "author": {
    "name": "Ray",
    "email": "[email protected]"
  },
  "main": "dist/r-collapse-vue.common.js",
  "files": [
    "dist"
  ],
  "keywords": [
    "Vue",
    "collapse"
  ],
  "publishConfig": {
    "registry": "https://registry.npmjs.org"
  },
  "repository": {
    "type": "git",
    "url": "[email protected]:DanceOnBeat/r-collapse-vue.git"
  }
}
  1. 最後執行npm publish即可完成釋出

每次釋出新版本之前,我們可以通過

npm version major/minor/patch -m 'xxx'

來修改版本號並且打上tag,此tag非NPM的dist-tag,而是Git的tag。一個版本對應一個tag,並通過

git push origin master --tags

將tag也推到遠端倉庫,這樣在倉庫中我們就能清楚地看到釋出的記錄,方便日後回滾之類的操作。具體的版本規則可以參考semver規範。

持續整合(CI)

當開發結束後,我們需要跑測試,測試通過後,還需要構建生成dist目錄,最後釋出到NPM。每次修改都做這樣一套操作實在繁瑣,並且容易遺漏步驟,這時候我們就需要使用CI將我們的流程自動化,我在這邊選擇了TravisCI。同時,我們還可以通過Codecov,將我們的單測報告上傳至Codecov伺服器,這樣就能同步更新Codecov的icon。

在配置CI時,我原本將生成docs的步驟也添加了進去,此時我們在deploy中會有兩個步驟,如下:

deploy:
  - provider: npm
    email: [email protected]
    api_key: $AUTH_TOKEN
    on:
      tags: true
      branch: master
    skip_cleanup: true
  - provider: pages
    skip_cleanup: true
    github_token: $GITHUB_TOKEN
    keep_history: true
    target_branch: master
    on:
      branch: master

這會造成一個問題是provider: pages會將CI伺服器生成的新的docs目錄push到我們的Github倉庫,這又會觸發一次CI,以至於無限迴圈。後來也沒找到合適的解決方案,又考慮到文件不經常更新,就將文件部署相關的部分從CI中移除了。如果大家有合適的解決方案,可以留言告訴我一下,不勝感激。

之前我們提到一個NPM釋出版本對應一個tag,因此我們可以在配置中新增

if: tag IS present

限定只在提交了tag才觸發一次自動化構建,這樣基本上就大功告成了。

總結

這是一次非常有趣的造輪體驗,程式碼雖然不難,但是過程中又學習到了很多新的東西,包括單元測試、文件編寫等等,希望這篇文章能給準備造輪或想要造輪的小夥伴提供一點幫助