預覽
元件庫官網
github地址
如果喜歡各位小哥哥小姐姐給個小星星鼓勵一下哈 ~~
完整專案目錄結構
git clone到本地安裝依賴後,執行npm run serve進行本地元件庫開發,npm run docs:dev進行元件庫官網開發。一般在src/demo.vue進行單個元件測試通過後,再引入到.vuepress/components中放入元件庫官網。
├─docs // vuepress開發目錄
│ ├─.vuepress
│ │ ├─components // 在markdown中可以使用的vue元件
│ │ ├─dist // vuepress打包目錄
│ │ │ ├─assets
│ │ │ │ ├─css
│ │ │ │ ├─img
│ │ │ │ └─js
│ │ │ ├─components
│ │ │ │ ├─basic
│ │ │ │ ├─form
│ │ │ │ ├─navigation
│ │ │ │ ├─notice
│ │ │ │ └─other
│ │ │ └─guide
│ │ │
│ │ ├─config.js // vurepess配置修改入口,包括左邊sidebar,右上方nav導航選單,favicon等
│ │ ├─style.style // 覆蓋vuerpress預設主題樣式
│ │ └─public //公共資源入口,如favicon
│ ├─static
│ │ ├─img
│ │ └─js
│ └─views // vuepress檢視檔案,格式是markdown
│ ├─components
│ │ ├─basic
│ │ ├─form
│ │ ├─navigation
│ │ ├─notice
│ │ └─other
│ ├─design
│ │ └─color
│ └─guide
├─src // 元件庫原始碼目錄
│ ├─button
│ ├─cascader
│ ├─collapse
│ ├─container
│ ├─datepicker
│ ├─form
│ ├─icon
│ ├─layout
│ ├─notice
│ ├─plugins
│ ├─slide
│ ├─tab
│ ├─step
│ ├─sticky
│ └─index.js // 元件庫原始碼元件入口檔案,執行npm run build的目標檔案
├─package.json // 與npm釋出相關,記錄版本號,包入口檔案地址
複製程式碼
學習元件庫製作會收穫
- 學習元件封裝技能,良好的介面設計, 掌握元件設計套路
- 夯實js/css基礎
- 深入對vue的理解
製作流程
- 元件設計/開發
- 釋出npm
- 製作官網展示
元件設計/開發
頻繁涉及到的vue api包括
- $children : 獲取當前元件子元件。
- $parent: 獲取當前元件父元件。
- $options: 用於當前 Vue 例項的初始化選項, 可以用此選項獲得元件的name。
- $refs: 一個物件,持有註冊過 ref 特性 的所有 DOM 元素和元件例項。
- $el: Vue 例項使用的根 DOM 元素。
- provide & inject :這對選項需要一起使用,允許一個祖先元件向其所有子孫後代注入一個依賴,注意不是響應式的。注入的物件可以是個vue例項的eventBus。
- $on: 元件監聽自定義事件。
- $emit: 元件觸發自定義事件。
- .sync:語法糖,單向資料流中,父元件監聽到子元件修改props的意圖後父元件修改傳入的props,用了.sync不需要顯式在父元件監聽元件內部觸發的自定義事件去修改值, 父元件只要寫:x.sync="bindValue", 注意此時子元件觸發的事件必須是"update:x"此語法糖才生效。
- updated 生命週期鉤子函式,由於資料更改導致的虛擬 DOM 重新渲染和打補丁,在這之後會呼叫該鉤子, 在父子元件通訊可能用到。
- beforeDestoryed/ destory 生命週期鉤子函式,destory後元件的所有的事件監聽器會被移除。注意:如果是自己在元件內部對dom增加了事件監聽,元件銷燬的時候需要自己手動接觸自己另外新增上去的監聽程式。而且元件銷燬,dom元素還被保留在頁面,需要手動清除,可以呼叫原生js api, node.remove()清除dom節點。
原生js api包括:
- target.addEventListener(type, listener[, useCapture])/removeEventListener 由於這是 DOM 0 規範的基本內容,幾乎所有瀏覽器都支援這個,而且不需要特殊的跨瀏覽器相容程式碼。
- Node.contains()返回的是一個布林值,來表示傳入的節點是否為該節點的後代節點。多用於事件監聽判斷是否點選了目標區域。
- window.scrollY 獲取文件垂直方向的滾動距離。
- Element.getBoundingClientRect() 返回元素的大小及其相對於視口的位置,返回一個物件,包括width/height/left/right/top/bottom。多用於計算定位。
技術點總結
元件設計的思想包括單資料流/ eventBus事件中心,核心是元件通訊。
- 單資料流: 資料的改變是單向的,即通過props的方式,只能讓父元件來修改資料,子元件不能主動修改props。這樣的例子如在collapse/tab/slide元件中,讓父元件來控制選中的值。單向資料流的思想讓資料修改更好設計,邏輯更加清晰。
- vue外掛開發:什麼時候用外掛開發?當元件不是顯式在程式碼中被呼叫,不是直接寫在template中,而是通過呼叫Vue原型鏈上的方法被掛載到文件中。 比如modal模態框/toast彈窗。外掛設計的基本思路是暴露一個install方法,這個方法中在vue原型鏈上增加一個自定義的方法X, X中引入元件a,通過Vue.extend(a)獲得元件構造器Constructor, 在通過new Constructor({propsData})獲得元件例項vm, 再掛載元件例項到文件中。
import modal from '../notice/modal'
export default {
install (vue, options) {
const Construtor = vue.extend(modal)
let modalVm // 保證全域性只有一個modal例項
let lastOption
vue.prototype.$modal = (options) => {
if (lastOption !== JSON.stringify(options)) { //! modalVm
modalVm = new Construtor({ propsData: options })
modalVm.$mount()
document.body.append(modalVm.$el)
}
lastOption = JSON.stringify(options)
modalVm.isVisible = true
}
}
}
複製程式碼
- eventBus:什麼時候用eventBus?當狀態的變化需要多個子元件被通知。如tab元件中,當選中的值發生變化,tab-head需要感知變化讓提示的短線做個動畫滑到選中的標籤下,tab-item需要感知變化讓文字變成選中樣式,tab-pane需要感知變化讓選中的皮膚出現。
- 遞迴:在級聯元件的設計中用到。類似函式fn中用setTimout(fn,millseconds)呼叫自己實現setInterval的遞迴效果。元件只要內部提供name屬性,就可以遞迴地呼叫自身。允許元件模板遞迴地呼叫自身。通過提供 name 選項,便於除錯,在控制檯可以看到可以獲得更有語義資訊的元件標籤。
- 媒體查詢 &flex佈局:響應式佈局的原理是媒體查詢和百分比佈局,介於某個尺寸的時候某個類名生效;跟佈局相關的大部分用到flex,非常好用。詳細看阮一峰老師教程
| 元件型別 | 元件|單資料流 | vue外掛開發 | eventBus| 原生js操作dom & 事件| 遞迴|媒體查詢&flex佈局| | :------| :------ | :------: |:------| :------| :------: |:------|:------| | 基礎| button按鈕 |- |-| -|- |- |- | | 基礎| icon圖示 |- |-| -|- |- |- | | 基礎| grid網格 |- |-| -|- |- | yes | | 基礎| layout佈局 |- |-| -|- |- | yes | | 表單| input輸入框 |- |-| -|- |- |- | | 表單| cascader級聯選擇器 | yes |-| -|- | yes |- | | 表單| form表單 |- |-| -|- |- |- | | 表單| datepicker日期選擇器 |- |-| -| yes |- |- | | 導航| tab標籤頁 |- |-| yes|- |- |- | | 導航| step步驟調 |- |-| -| - |- |- | | 通知| toast提示 |- |yes| -|yes |- |- | | 通知| popover彈出框 |- |-| -| yes | - |- | | 通知| modal模態框 |- |yes| -|yes | - |- | | 其他| collapse摺疊皮膚 | yes |-| yes|- |- |- | | 其他| slide輪播圖 | yes |-| -|- |- |- | | 其他| sticky粘滯 |- |-| -|- |- |- |
元件設計三要素
- props:可以參考餓了麼或者antd, 需要從使用者的角度考慮怎麼使用方便和擴充套件性好,一般需要校驗型別和有效值,設定預設值。
- slot:插槽內容分發,使用作用域插槽讓slot也可以獲得元件內部方法,讓使用者自定義的內容頁能呼叫元件內部方法,比如popover彈出框中使用者想自己加個按鈕手動呼叫關閉。
- event: 元件事件。從使用者角度考慮,比如datepicker元件中使用者想在日期皮膚被開啟或這關閉的時候進行操作。這種一般用在互動類UI元件。
舉個例子
複雜元件datepicker開發思路
-
在原有的popover元件上開發
點選一個元素A(輸入框)後可以彈出元素B(日期皮膚) -
生成日期皮膚
生成7*6=42個日期,6行是為了確保一個月都能在皮膚上完整顯示。這裡計算最方便的做法是用時間戳,計算出這個月第一天時間戳和這一天周幾,就可以一次性計算出這42個日期。不用算上個月下個月分三段算,這樣的問題是還要考慮邊界情況,如剛好出現上一年下一年等,麻煩容易出bug。這42個日期我們在computed用visibleDays表示。visibleDays () { let { year, month } = this.display let defaultObj = new Date(year, month, 28) var curMonthFirstDay = helper.getMonthFirstDay(defaultObj) var curMonthFirstDayDay = helper.getDay(curMonthFirstDay) === 0 ? 7 : helper.getDay(curMonthFirstDay) let x = curMonthFirstDayDay - 1 // 前面需要補多少位 var arr = [] for (let i = 0; i < 42; i++) { arr.push(new Date(curMonthFirstDay.getTime() + (-x + i) * 3600 * 24 * 1000)) } return arr }, 複製程式碼
-
props接受value, 型別是date
日期皮膚上的日期渲染的時候加上一個計算的class, 分別加上'today','selected-date','available','prev-month','next-month',進行樣式上的區分 -
實現選中日期
告訴父元件修改資料意圖讓父元件修改傳入的props,對應使用我們元件的時候使用, 這裡的基礎知識是元件上的v-model是個語法糖,v-model="x"會被解析成:value="a" @input="a=$event"。同時皮膚上輸入框顯示的資料也要跟著變化,所以這裡用計算屬性,如在computed中用formattedValue表示。formattedValue: { return this.value instanceof Date ? helper.getFormatDate(this.value) : '' } 複製程式碼
-
實現點選上一年/月,下一年/月
我們需要知道當前展示的是哪一年哪一個月,這個資料是元件內部維護的,所以在data申明一個display物件display: { year: (this.value && this.value.getFullYear()) || new Date().getFullYear(), month: (this.value && this.value.getMonth()) || new Date().getMonth() } 複製程式碼
點選的時候即修改display物件的year/month,因為visibleDays也是計算屬性,依賴display物件,所以點選上一年/月,下一年/月,渲染的日期也跟著變。
-
實現選擇年
年皮膚的製作,生成12個年,點選第1(12)個年渲染出上(下)12個年。這裡只需要給渲染出來的年的第一個和最後一個dom元素繫結事件,事件監聽程式傳入當前點選的元素的值,即可計算出上或下一個12年。 同理點選年的時候用$emit通知父元件修改value -
實現選擇月
直接寫死12個月份,同理點選月的時候用$emit通知父元件修改value -
增加住皮膚上【今天】和【清空】的按鈕
點選的時候用$emit通知父元件修改value,new Date()和'' -
細節處理
使用者選中完日期後要關閉皮膚 使用者選了年後點選周圍空白區域日期皮膚關閉,第二次點選進來應該預設展示日皮膚 -
使用者可以修改輸入框裡面的值,需要判斷有效性
有效的話$emit通知父元件改值,無效的話當失去焦點的時候變回原來的值,這裡需要用原生js去給input修改value。注意這裡直接改formattedValue的話無效,雖然輸入框的值繫結了:value="formattedValue",但是因為formattedValue是計算屬性,依賴於this.value,在使用者輸入無效值的情況下this.value不會改變,因此介面不會被更新,所以需要手動改value的值。setValueManually ($event) { if (!helper.isValidDate($event)) { this.$refs.inputWrapper.$refs.input.value = this.isDate(this.value) ? helper.getFormatDate(this.value) : '' return } this.$emit('input', new Date($event)) } 複製程式碼
-
完善
給彈出日期皮膚和關閉日期皮膚增加元件自定義事件, 即呼叫$emit觸發'showDatepicker'和'closeDatepicker'事件。
釋出npm
- 使用vue cli3 的庫模式打包程式碼,修改package.json 中的"build": "vue-cli-service build --target lib --name sakura src/index.js",打包後輸出umd構建版本, 參考vue cli。
什麼是umd? 統一模組定義,可以相容common.js(node端規範)/ AMD(瀏覽器端規範)/ ES6(node端不完全支援)等多種模組化方案,確保程式碼在各種環境下能被執行。
File Size Gzipped dist/sakura.umd.min.js 13.28 kb 8.42 kb dist/sakura.umd.js 20.95 kb 10.22 kb dist/sakura.common.js 20.57 kb 10.09 kb dist/sakura.css 0.33 kb 0.23 kb 複製程式碼
- 在package.json指明模組入口"main":"dist/sakura.umd.min.js"
"name": "heian-sakura-ui", "version": "0.0.6", "private": false, "main":"dist/sakura.umd.min.js", "description": "an UI framework based on Vue.js", 複製程式碼
- 在npm 上註冊一個使用者
- 在命令列輸入,注意每次釋出都要修改package.json中的 "version": "0.0.x","private"必須設定成false才能釋出
npm adduser // 提示輸入註冊的使用者名稱 npm publish 複製程式碼
官網製作
使用vue press
-
在原有專案中使用
# 安裝依賴 npm install -D vuepress # 建立一個 docs 目錄 mkdir docs 複製程式碼
在package.json中進行指令碼配置
{ "scripts": { "docs:dev": "vuepress dev docs", "docs:build": "vuepress build docs" } } 複製程式碼
然後執行npm run docs:dev即可訪問
-
簡單配置
在docs/.vuepress下新建檔案config.jsmodule.exports = { base:'/sakura-ui/', title: 'Sakura UI', description: 'Inspiration from heian sakura', head: [ ['link', { rel: 'icon', href: '/favicon.ico' }] ], themeConfig: { nav: [ { text: 'Home', link: '/' }, { text: 'Github', link: 'https://github.com/Firenzia/sakura-ui/' }, ], sidebar: [ { title: '開發指南', collapsable: true, children: [ 'views/guide/install.md', 'views/guide/get-started.md' ] }, { title: '設計', collapsable: true, children: [ 'views/design/color/', ] }, { title: '元件', collapsable: true, children: [ 'views/components/basic/', 'views/components/form/', 'views/components/navigation/', 'views/components/notice/', 'views/components/other/' ] }, ] } } 複製程式碼
-
使用vue元件
官網中提到,所有在 .vuepress/components 中找到的 *.vue 檔案將會自動地被註冊為全域性的非同步元件,可以在markdown中引用, vue檔案中的程式碼高亮我用的是vue-highlightjs 檢視這裡 -
編寫文件
由於所有的頁面在生成靜態 HTML 時都需要通過 Node.js 服務端渲染,對於SSR 不怎麼友好的元件(比如包含了自定義指令),你可以將它們包裹在內建的 ClientOnly 元件中,而且注意因為是ssr,元件內部beforeCreate, created生命週期鉤子函式訪問不到瀏覽器 / DOM 的 API,只能在beforeMount和mounted中呼叫。--- title: 'Basic 基礎' sidebarDepth: 2 --- ## Icon 圖示 <ClientOnly> <sakura-icon/> <font size=5>Attributes</font> | 引數| 說明 | 型別 | 可選值 | 預設值 | | :------ | ------ | ------ | ------ | ------ | | name | 圖示名稱 | string |- | - | | color | 圖示顏色, 支援常見顏色和十六進位制顏色 | string |- | - | </ClientOnly> 複製程式碼
-
覆蓋預設主題樣式
在.vuepress下新增style.styl進行覆蓋。 -
部署到github
官網上介紹的很清楚,點這裡。 在專案根目錄下新增deploy.sh,windows下直接命令列執行./deploy.sh即可釋出到github pages上。
結語
如果你能看到這裡,非常感謝,第一次寫文章,希望大家多多提出意見。元件庫還有很多細節需要完善,比如裡面css的類名命名我沒做的很規範,大部分元件都是自己測試沒有測到複雜或特殊場景,還有很多功能還沒支援。通過這段時間製作元件庫,自己的技術有了一定提升,官網的展示融入了自己的一點想法和設計,希望大家喜歡~~ 謝謝!