Update 2019/12/12
元件編寫規範
前言
隨著js程式設計進入工程化紀元,程式碼模組化,元件化,成為工程的具體落地方法。
最近使用vue全家桶做了一個類似於iconfont的網站,在做的過程中關於元件有了一些思考和總結,為了鞏固,寫個小結。
一 元件型別
根據我已知的元件的編寫方式,有四種介紹給大家:
1.1 基本元件
最基本的元件由一個.vue檔案構成,其中包含了基本的tempplate,script, style三大元素。
可以通過props接收引數,$emit事件bus向父傳遞引數。
比較懶得我一般會在dev階段在components資料夾裡放置一個Demo.vue。寫好基本的元素以及元件註釋。
import後在元件的components中宣告就可以在模板裡標籤呼叫了。
<template>
<!-- 指南中推薦元件名應該始終為多個單詞的,根元件App,<transition>、<component>之類除外 -->
<Header></Header>
<shop-cart></shop-cart>
</template>
components: { Header, ShopCart }複製程式碼
這裡比較注意的是元件命名規範應該嚴格參考vue風格指南。讓駝峰和短橫線在該使用的地方使用。
1.2 構造元件
構造類元件主要是用在想要通過js動態呼叫元件的場景裡,由一個該元件資料夾比如notice,中包含Notice.vue, index.js兩個檔案組成。
demo-notice
DemoNotice.vue
index.js
複製程式碼
vue檔案就是編寫基本的vue檔案
index.js中
- 使用vue的extend方法來返回一個Notice.vue的構造器物件,在物件原型上新增一些常用方法比如close
- 編寫Notice函式物件,在函式中將構造器物件進行例項化(最基本的:創造一個div元素掛在el屬性上,你也可以根據場景需求為其配置上store,router,通過.$store獲取)
- 將該例項的el屬性上的元素append到body(或者你想要的新增的元素)中
- 將函式傳來的引數資料處理後對例項中資料進行賦值(等於props傳值,同時此處相當於做了每次開啟元件時的週期。)
- 將這個函式物件作為default返回
/** 專案新增模態框 */
import Vue from 'vue'
import proModal from './ProModal.vue'
import store from '@/store/index'
import router from '@/router/index'
let proModalInstance
export default {
open (modalType, packageInfoDatail) {
if (!proModalInstance) {
proModalInstance = new ProModalCreater({
el: document.createElement('div'),
store: store,
router: router
})
document.body.appendChild(proModalInstance.$el)
}
proModalInstance.$data.modalType = modalType
proModalInstance.$data.options4 = []
proModalInstance.visible = true
modalType === 'modify'
? (() => {
proModalInstance.$data.packageInfoDetailTemp = packageInfoDatail
proModalInstance.$data.headerName = '修改專案'
proModalInstance.$data.value9 = []
packageInfoDatail.coUsers.map((item) => {
proModalInstance.$data.value9.push(item)
proModalInstance.$data.options4.push(item)
})
})()
: proModalInstance.init()
if (modalType === 'create' && packageInfoDatail) {
proModalInstance.fromPath = packageInfoDatail
}
// proModalInstance.$data.callback = callback
}
}複製程式碼
呼叫:
- 可以通過在main.js中呼叫然後將其掛載在Vue原型上再去元件this中呼叫方式呼叫
Vue.prototype._message = obj => {
obj.duration = messageDurationTime
ElementUI.Message(obj)
}
this._message.info('why so serious?')複製程式碼
- 也可以在不同元件中引入然後再去呼叫
import ProModal from '@/components/common/proModal'
methods: {
createPro () {
// 開啟模態框
ProModal.open('create')
},
}複製程式碼
1.3 全域性元件
全域性元件主要是用在一些通用元件想要在各處元件裡使用標籤呼叫的場景,檔案結構與構建元件相同。
vue檔案就是編寫基本的vue檔案。(該注意的點還是多注意下,全域性對通用,顆粒的要求更高一些)
index.js中
- 引入元件ButtonComponent並且編寫一個元件物件用於匯出,該元件物件中有一個install方法
- install方法中核心:Vue.component("Button", ButtonComponent);
- 返回該元件物件
import NoContentComponent from './NoContent.vue'
// 新增install方法 (外掛方法)
const NoContent = {
install: function (Vue) {
Vue.component('NoContent', NoContentComponent)
}
}
// 匯出Buttonexport
default NoContent複製程式碼
在全域性main.js中引入元件物件使用Vue.use()註冊該元件
Vue.use(NoContent)複製程式碼
在各個元件中通過標籤直接呼叫
<no-content v-if="!Array.isArray(iconLibNowDetail.icons)">
<template slot="default">
<span>還沒有上傳圖示哦</span>
<upload-btn :toPath="'/upload?type=iconLib&id=' + libId"></upload-btn>
</template>
</no-content>複製程式碼
1.4 指令類元件
指令類主要是使用Vue.directive方法註冊一個指令,在指令中bind的方法中去例項元件並新增到響應dom處。這裡不多說,因為我沒用到。(想看怎麼用的去看參考Vue元件的三種呼叫方式)
其結構類似於全域性元件,只是呼叫是通過在某個元素上的指令屬性進行呼叫。
二 元件設計原則
好了,知道怎麼寫一個基本的元件了,路程剛剛開始,那麼如何寫好一個元件呢。如果你寫的元件是為了目的而寫不考慮設計原則,相信很快就會被你自己所拋棄,然後重構元件。(小夥伴說我這一節寫的步驟有些亂不太好,後面會改進)
先說下我的元件設計思路:
- 思考元件型別:是基礎元件,還是業務元件
- 思考元件核心:該元件核心是什麼,核心代表著極低的變動,甚至不變
- 思考元件顆粒度(複用性): 該元件是基礎元件會被多處複用 ?設計時儘量最小化,減少ui資料繫結使其靈活輕巧 : 業務元件則可以適當ui和資料繫結,只通過一部分傳參進行update
- 思考元件插槽:插槽的存放位置,後備內容,資料等
- 思考元件資料傳遞(可配置性): 是否需要資料傳遞 ?是否會深層傳參 ?採用vuex :注意props資料不要被元件內直接改變,$emit向上傳遞資料 :無資料傳遞,僅作為ui層複用
- 元件通訊這裡可以再更多考慮,區域性元件處巢狀通訊是否可以使用bus來通訊,深層傳參是否使用inject來傳入,包括是否使用this.$refs.parent.xxData來獲取資料。這些都需要去考慮,當然一定不要被這些便利的屬性觸角誘惑而不去使用props/$emit,這樣只會使元件資料邏輯變得不易閱讀和查詢。
- 思考元件資料健康(記憶體管理,watcher下儘量扁平資料存放,動態請求重新整理)
- 思考元件效能(優化,提取,銷燬)
Update 2019/12/12
元件編寫規範
1. 屬性順序:
一個持久化的健康元件,必然是遵循了統一風格來編寫的,所以一個元件的內部編寫規範非常的重要,先分享下我的元件內部屬性編寫順序
<template>
<header v-show="headerShown">
<header-select
source="demo"
v-if="true"
v-show="true"
v-once="false"
v-modal=""
@change=""
// v-html=""
isDemo="">
</header-select>
</header>
</template>複製程式碼
<script>
import HeaderSelect from '@/components/header-select'
export {
name: 'demo-header',
components: { // 模板依賴(元件內使用的資源)
HeaderSelect
},
extends: {}, //組合
mixins: {}, // 組合
props: { // 元件介面
headerShown: {
type: Boolean,
default: true
}
},
data () {
return {
}
},
computed: {},
watch: {},
methods: {},
// 生命週期
beforeCreated () {
},
created () {},
...
}
</script> 複製程式碼
2. 元件通訊單向流
為了保持元件間的純淨性,以及對資料流向的清晰,我們儘量避免使用 instance.$parent[xxChildCoponentsName] 來獲取其他元件的資料並修改。
從這個角度我們再返回看看前面的元件設計思路, 就可以得知一個頂層模組元件中所擁有的一些子級元件,這個要求我們從源頭做起,從一開始就要畫好程式碼設計流程圖,一定要清楚模組之間的流向以及資料的流向,將一個需求進行拆分,解析出資料和內容的層級,並將其清晰的做出分配以及指向,並且要考慮到兩者的邊界情況以及可能擁有的複雜場景。這種在下面的複雜元件設計中尤為重要。
3. 合理且全面的出入口註釋設計
假設你要給一個專案寫一個公用的 CommonTable.vue ,那在做好了其層級定位及核心內容資料的解析後,對出入口資料的設計就顯得極為重要了,別人可以不關注你內部的 data 或是變數,但是對於要呼叫的元件的自定義特性(Prop),別人一定是需要知道這個元件都有哪些可用的特性的。所以,假如你沒有文件(誰會專門為了個專案去寫元件文件呢),那麼在script最前端的元件註釋就是元件最好的文件。(改天放一個元件特性註釋模板上來)
三 複雜元件設計
這一節起的有點節題黨,這裡只是分享一點點點總結,不敢談設計,後續有好的總結會更新上來,大佬們輕拍哈哈
元件在一些場景中會變得複雜,其複雜性分為元件本身的複雜和被應用場景的複雜。
本身比較複雜的元件如dataTimerPicker,Form之類的擁有複雜的配置項屬性以及methods,events,並且其中會包含子元件,子元件的slot,attributes,methods也會很多。
這裡僅僅分享三個場景:
1. 我在做一個IconList,當時想要一次性在元件中全部做好,就不想再多抽出子元件,結果發現會寫的很冗餘。於是很快就抽出了icon元件,IconList接受icons陣列資料,在v-for中將item物件再傳入icon元件,icon中通過props拿到iconData,最終用來渲染出最終顯示的icon。
當然還可以再對icon進行子元件抽取的,我目前還是採用配置引數iconModalType方式+v-if來區分專案中和圖示庫內在不同許可權下,三種modal層的顯示。後面可以繼續使用mixIn(IconModal.vue中使用)+呼叫時<icon-modal :modalType="icon.modalType">方式來載入。
組合大於繼承,不用一次性去寫一個龐然大物,這樣會在複用的時候束手束腳失去靈活性。
2. 我在做一個FilterGroup,確定了其核心是InputSearch結果到了後面發現還是會遇到核心不確定場景。於是這個時候FilterGroup只為ui服務,子元素可以通過slot來載入,slot中通過name將子元件放置到對應位置。下面是個demo,我當時就將InputSearch和Sort只作為了FilterGroup中的元素來處理了,導致了後續需要通過很長的配置項在group上去確定groupElement上的屬性,使Group本身屬性被混淆減弱了自身含義,增加了複用時理解難度。
<!-- group合理設計應該為使用slot插入需要的groupElement而不是直接去整合div,即使是非常固有化的元素,如其中的兩個元素:排序和搜尋元素 -->
<btn-group inputPlaceVal="請搜尋圖示" :useSort="true" @sort="proSort" :sortOptions="options" @search="searchIcon" >
<template slot="body">
<div class="radius-btn" v-if="_isProManager() >= 2" @click="createPro">
<img :src="iconAddLib" alt="">
</div>
</template>
</btn-group>複製程式碼
正確的是應該去考衡抽出的必要性,假如該元素不復雜不需要傳遞資料自身無複雜方法,固定為ui項,就可以通過配置屬性來取決其是否顯示,比如element的input的icon,使用prefix-icon
和 suffix-icon
屬性在 input 元件首部和尾部增加顯示圖示,當然在句尾說清了:也可以通過 slot 來放置圖示 。因為slot伴隨著子元件的編寫,也是有代價的,所以對於一些預設最小項,個人認為可以內建編寫好,通過配置屬性來顯示。
當然,可以看到,slot對於一個元件是多麼的重要,一個複雜的子元件,必定不是一次性在元件內寫好的,而是經過提取優化思考得到的一個組合結果,並且經得起復用和靈活擴充套件。
3. 我在做一個BatchOperate,批量操作元件時發現自己需要通過獲得兄弟元件iconlist中被批量選擇的icon的id資料,並且將這些icon加入ShopCart,這個時候對於資料以及狀態的考量就來了。
最後選用了vuex加LocalStorage方式來解決了這個資料通訊的問題。(有點晚了,先不細說...)
這幾個場景事最想表達的是:
在模組化思想的今天,這種抽出組合的思維是很靈活的,可以在寫元件的時候多去思考下模組化,包括公共方法,公共樣式,vue的mixIn,scss的mixIn,公共變數倉庫store集中處理,元件最下化,讓重複性程式碼模組化抽出,再通過配置靈活組合。這種做法可以讓程式碼在後續的維護中非常清爽,一鍵修改專制各種公共改動,再也不用跑到各個地方去做一下下的修改,並且避免了耦合帶來的深層程式碼纏繞,可以說真的很好用了。
今天先寫到這,第三節總結的不是很好,只是先羅列出自己之前想到的一些問題和處理,具體的優化想法會和組內小夥伴討論後再更新的。
Update 2019/12/12
元件模組化到底如何去做,我目前是這樣做的,首先你要有清晰度的程式碼目錄層級,比如
page // 特性頁
page // 專門放置一級頁面
market-demo
compontens
data.js
market-child
components
market-child-tree.vue
market-child.vue
market-select.vue
market-modal.vue
market-demo.vue
複製程式碼
components // 公共元件
components
demo-button
demo-button.vue
demo-total
demo-total.vue
複製程式碼
hhhh,其他的都下次更新上去,沒帶寫的總結