本文僅僅針對vue系列做探討, 項目傾向於大量增刪改查的後臺管理項目 組件二封不是換一種寫法 大量帖子的組件二封都是用json, 然後將prop/emit/slot通過一種json配置來完成. 不是說這種寫法絕對不好, 畢竟還真有這種場景, 比如動態表單. 但是, 一般理性而論, 失去了IDE的代碼 ...
目錄
本文僅僅針對vue系列做探討, 項目傾向於大量增刪改查的後臺管理項目
組件二封不是換一種寫法
大量帖子的組件二封都是用json, 然後將prop/emit/slot通過一種json配置來完成.
不是說這種寫法絕對不好, 畢竟還真有這種場景, 比如動態表單.
但是, 一般理性而論, 失去了IDE的代碼提示, 失去了eslint的規範檢查, 不夠靈活且不方便調試.
順帶一提, 還有this指向問題.
封裝的意義在於更好的維護, 而不是為了封裝而封裝
組件二封應當具備哪些條件
-
團隊人員穩定
二封組件過多, 相當於每個人都要花時間瞭解二封的內容, 人員流動大, 二封可能會得不償失
-
團隊有一定的樣式規範
通往羅馬的路不止一條, 規範的作用是只走一條, 比如說我們有如下要求:
- 檢索按鈕應當在檢索條件下另起一行, 並置於右側
- 所有table應當具有斑馬條紋和border
- select下拉組件應當和input組件等寬
- 分頁預設10, 可選10/20/50, 居右, 變數名: pageSize(大小)/pageNum(頁碼)
上述規範僅舉例, 未必合理. 擁有類似這樣的規範, 才是二封的前提
-
團隊有一定的編碼規範
如上述樣式規範, 如果沒有編碼規範, 每個規範實現方式都千奇百怪
那麼根據上述, 我們有如下對應的編碼規範
- 檢索條件另起一行功能應當用el-row+"text-align: right"實現, 而不是float實現
- table的斑馬條紋應當用ElTable的stripe和border屬性實現, 禁止自己寫css樣式
- select下拉組件與input等寬, 應當由全局樣式處理, 而非每個select組件上添加樣式
- 分頁可選參數去某個地方讀取配置, 使用float固定居右
-
團隊技術領導人具有一定能力
多了不說, vue2的話, mixin, 生命周期, 自定義指令, provide/inject, 這些官方指南上介紹的功能都應該熟悉, 還要熟悉vue的官方風格指南
vue3的話, 二封最好ts, 熟悉ts, 非同步組件, 單文件組件, 自定義指令, 熟悉官方風格指南
我覺得這些就夠了, 差一點也沒關係, 二封沒那麼難. 全都是在調用原有組件的api而已
PS: 並不是說只會這些就夠了
-
公司層面的支持
好的封裝, 不論是二封, 工具類, 規範設計, 對於小公司來說都是很大的開銷, 如果公司沒這個打算, 還是自己搗鼓著玩吧. 就像某boss的言論: "我覺得代碼寫的太好或太差都不行, 別人看不懂, 還不如沒寫". 這種情況下就需要審時度勢了
我認為的二封應當有哪些作用
- 實現團隊中90%以上的功能全部使用二封組件
- 過濾掉原有組件中不推薦使用的prop/methods(expose)/slot
- 樣式處理, 如檢索條件的佈局, 列表的佈局, 常規表單的佈局等等等等
- 自動的格式轉換, 如日期格式應當僅使用字元串格式, 以下是幾個組件常見日期格式, 幾乎每次傳給後端都需要自己處理一下, 很是麻煩:
- element-ui: Date/Date[]
- element-plus: Date/number/string/any[]
- antd vue 1.x: moment
- antd vue 2.x: dayjs
- 功能拓展, 列舉一下我見到的一些需求, 有些功能是部分場景有的, 有些則是全部場景都要的, 全部場景都要, 就要儘量禁止開發人員關閉此功能
- table列支持拖拽調整列寬
- table支持自定義列
- 日期選擇器支持快速選擇, 輸入時支持中文冒號
- 數字輸入時, 自動提示大寫, 如"一萬兩千三百四十二"
- 圖片/視頻/文檔預覽功能(這種需求就千奇百怪了)
- 所有彈窗支持可拖拽
- 功能屏蔽, 列舉一下我見過的客戶不要的, 組件預設啟用的功能:
- 數字輸入框, 屏蔽+/-號和控制按鈕
- 彈窗幹掉右上角的x號
- 彈窗不允許點擊空白處關閉
- 靈活性, 業務人員只需要關心"有沒有", 而不需要關心樣式
- 如檢索表單, 查詢/重置按鈕位置
- 如表格支持自定義列
- 如彈窗的可拖拽, 亦無需業務人員處理
- 如按鈕封裝, 業務人員無需關心使用什麼按鈕圖標, 按鈕裡面應當是什麼文字
二封的好處
先來一個列表頁demo來看看效果(Vue2)
<template>
<div>
<ly-form :loading="loading" search-form @reset="handleReset" @search="handleSearch">
<ly-input label="客戶姓名" v-model="searchForm.title"/>
<ly-input label="客戶手機號" v-model="searchForm.title"/>
<template #append-buttons>
<ly-btn-create @click="$refs.edit.init()"/>
</template>
</ly-form>
<ly-table :data="tableData" :loading="loading" :paging="paging" @paging="handleSearch">
<ly-table-index/>
<ly-table-column label="客戶姓名" prop="name"/>
<ly-table-column label="客戶類別" dict-code="sys_xxx_type" prop="type" width="120"/>
<ly-table-column label="客戶消費額" prop="consumption" sortable width="100"/>
<ly-table-column label="客戶活躍度" prop="loginDays" sortable width="100"/>
<ly-table-action width="330">
<template #default="{row}">
<ly-action-detail @click="$router.push({name: 'CustomerDetail', query: {id: row.id}})"/>
<ly-action-modify @click="$refs.edit.init(row)"/>
<ly-action-remove @click="handleRemove(row.id)"/>
</template>
</ly-table-action>
</ly-table>
<customer-edit ref="edit" @ok="handleSearch"/>
</div>
</template>
<script>
import LySearchMixin from '@/mixins/ly-search-mixin'
import {customerService} from '@/api/biz/xxx'
import CustomerEdit from '@/views/biz/dataset-manage/component/dataset-edit'
export default {
name: 'CustomerManage',
components: {CustomerEdit},
mixins: [LySearchMixin],
data: () => ({
api: {search: customerService.search, remove: customerService.remove}
})
}
</script>
來讓我們梳理一下這段代碼的封裝內容: