使用?render?函數(shù)封裝高擴(kuò)展的組件
需求:
后臺(tái)管理中常常有如下布局的數(shù)據(jù)展示需求:
像表格又不是表格,像表單又不是表單,實(shí)際上樣子像表格,呈現(xiàn)的數(shù)據(jù)是一個(gè)對(duì)象,和 form 的綁定的值一樣,我將其稱(chēng)為表單式表格。
樣式深的列是標(biāo)題,淺的列是標(biāo)題對(duì)應(yīng)的取值,數(shù)據(jù)往往是服務(wù)器返回的,標(biāo)題往往是定寬的,取值可能各種各樣,比如顯示一張圖片,值為 01,需要顯示是與否,有時(shí)候需要添加一個(gè)修改按鈕,讓用戶(hù)能修改某些值,還需要設(shè)置某一列跨越幾列。
先來(lái)看看一個(gè)基于 element ui
的實(shí)現(xiàn)
不好的實(shí)現(xiàn):
在接手的項(xiàng)目看到一個(gè)實(shí)現(xiàn),先看使用方式
<FormTable :data="lessonPackageArr" :fleldsInfo="lessonPackageInfo" :maxColumn="3" label-width="120px"> <template #presentedHours="{ data }"> <div class="flex-box between"> <span> {{ data.presentedHours }} </span> <span class="column-btn" @click="editPresentedHours(data)">修改</span> </div> </template> <template #gifts="{ data }"> <div class="flex-box between"> <span> {{ data.gifts }} </span> <span class="column-btn" @click="editPresentedHours(data)">修改</span> </div> </template> </FormTable>
lessonPackageInfo 對(duì)象如下結(jié)構(gòu):
// 一個(gè)對(duì)象,用于配置標(biāo)題列和標(biāo)題列對(duì)應(yīng)的字段 // type 指定值的類(lèi)型,現(xiàn)在組件內(nèi)部設(shè)置可能顯示哪些類(lèi)型的值了 // 對(duì)于服務(wù)其返回 1 0 需要顯示 是否的數(shù),提供一個(gè) map_data 來(lái)映射 // column 屬性設(shè)置跨列 // 需要自定義顯示內(nèi)容 提供 slot lessonPackageInfo: { orderType: { type: 'option', desc: '課時(shí)包類(lèi)別', map_data: { 1: '首單', 2: '續(xù)費(fèi)', 5: '贈(zèng)課' } }, combo: { type: 'text', desc: '套餐名稱(chēng)' }, presentedHours: { type: 'text', desc: '贈(zèng)送課時(shí)', slot: true }, price: { type: 'text', desc: '標(biāo)準(zhǔn)價(jià)格' }, gifts: { type: 'text', desc: '贈(zèng)送禮物', column: 3, slot: true }, }
- props 不夠直觀,配置項(xiàng)多
- 不是完全數(shù)據(jù)驅(qū)動(dòng)
為何組件的配置項(xiàng)多不好?
對(duì)于這種需求很固定,組件的輸入即 props
應(yīng)該要最小化,組件功能要最大化,盡量給 props 提供默認(rèn)值,這樣才能提高團(tuán)隊(duì)的開(kāi)發(fā)效率。
為何不是完全的數(shù)據(jù)驅(qū)動(dòng)不好?
這個(gè)組件不是完全數(shù)據(jù)驅(qū)動(dòng)的,需要自定義顯示列是,需要編寫(xiě)模板。
如果需要自定義的列很多,就要寫(xiě)很多模板代碼,想要再提取,只能再次封裝組件,不提取,模板代碼可能會(huì)膨脹,你可能經(jīng)??吹絼?dòng)輒 500 行一行的 template
?而膨脹的模板代碼,讓組件維護(hù)變得困難,需要 template
和 js 代碼之間來(lái)回切換。再者,增加一列自定義的數(shù)據(jù),起碼要修改兩個(gè)地方。
為何需要完全的數(shù)據(jù)驅(qū)動(dòng)?
雖然有 slot 來(lái)擴(kuò)展組件,但是我們?cè)趯?xiě)業(yè)務(wù)組件時(shí)候應(yīng)該少用,而是盡量使用數(shù)據(jù)驅(qū)動(dòng)模板。因?yàn)閿?shù)據(jù)是 js 代碼,當(dāng)組件代碼膨脹時(shí),很容易把 js 代碼提取成單獨(dú)的文件, 而想要提取 slot 的代碼,只能再封裝組件。
三大前端框架的設(shè)計(jì)理念都是數(shù)據(jù)驅(qū)動(dòng)模板,這是它們區(qū)別于 jQuery 的重要特征,也是我們封裝業(yè)務(wù)組件時(shí)優(yōu)先遵循的原則。
看了組件使用的問(wèn)題,再看組件的代碼:
<template> <div v-if="tableData.length" class="form-table"> <div v-for="(data, _) in tableData" :key="_" class="table-border"> <el-row v-for="(row, index) in rows" :key="index"> <el-col v-for="(field, key) in row" :key="key" :span="getSpan(field.column)"> <div v-if="(field.disabled && data[key]) || !field.disabled" class="column-content flex-box between"> <div class="label" :style="'width:' + labelWidth"> <span v-if="field.required" class="required">*</span> {{ field.desc }} </div> <div class="text flex-item" :title="data[key]"> <template v-if="key === 'minAge'"> <span>{{ data[key] }}</span> - <span>{{ data['maxAge'] }}</span> </template> <template v-else-if="key === 'status'"> <template v-if="field.statusList"> <span v-if="data[key] == 0" :class="field.statusList[2]">{{ field.map_data[data[key]] }}</span> <span v-else-if="data[key] == 10 || data[key] == 34" :class="field.statusList[1]"> {{ field.map_data[data[key]] }} </span> <span v-else :class="field.statusList[0]">{{ field.map_data[data[key]] }}</span> </template> <span v-else>{{ field.map_data[data[key]] }}</span> </template> <slot v-else :name="key" v-bind:data="data"> <TableColContent :dataType="field.type" :metaData="data[key]" :mapData="field.map_data" :text="field.text" /> </slot> </div> </div> </el-col> </el-row> </div> </div> <div v-else class="form-table empty">暫無(wú)數(shù)據(jù)</div> </template> <script> import TableColContent from '@/components/TableColContent' export default { name: 'FormTable', components: { TableColContent, }, props: { // 數(shù)據(jù) data: { required: true, type: [Object, Array, null], }, // 字段信息 fleldsInfo: { required: true, type: Object, // className: { type: "text", desc: "班級(jí)名稱(chēng)", column: 3 }, }, // 最多顯示列數(shù) maxColumn: { required: false, type: Number, default: 2, }, labelWidth: { required: false, type: String, default: '90px', }, }, data() { return {} }, computed: { tableData() { if (!this.data) { return [] } if (this.data instanceof Array) { return this.data } else { return [this.data] } }, rows() { const returnArray = [] let total = 0 let item = {} for (const key in this.fleldsInfo) { const nextTotal = total + this.fleldsInfo[key].column || 1 if (nextTotal > this.maxColumn) { returnArray.push(item) item = {} total = 0 } total += this.fleldsInfo[key].column || 1 item[key] = this.fleldsInfo[key] if (total === this.maxColumn) { returnArray.push(item) item = {} total = 0 } } if (total) { returnArray.push(item) } return returnArray }, }, methods: { getSpan(column) { if (!column) { column = 1 } return column * (24 / this.maxColumn) }, }, } </script>
有哪些問(wèn)題?
- 模板有太多的條件判斷,不優(yōu)雅
- 自定義顯示列,還需要在引入
TableColContent
,增加了組件復(fù)雜性
TableColContent
內(nèi)部還是對(duì)配置項(xiàng)的 type 進(jìn)行條件判斷
部分代碼:
<span v-else-if="dataType === 'image' || dataType === 'cropper'" :class="className"> <el-popover placement="right" title="" trigger="hover"> <img :src="metaData" style="max-width: 600px;" /> <img slot="reference" :src="metaData" :alt="metaData" width="44" class="column-pic" /> </el-popover> </span>
分析完以上實(shí)現(xiàn)的問(wèn)題,看看好的實(shí)現(xiàn):
好的實(shí)現(xiàn):
先看使用方式:
<template> <ZmFormTable :titleList="titleList" :data="data" /> </template> <script> export default { name: 'Test', data() { return { data: {}, // 從服務(wù)器獲取 titleList: [ { title: '姓名', prop: 'name', span: 3 }, { title: '課堂作品', prop: (h, data) => { const img = (data.workPic && ( <ElImage style='width: 100px; height: 100px;' src={data.workPic} preview-src-list={[data.workPic]} ></ElImage> )) || '' return img }, span: 3, }, { title: '作品點(diǎn)評(píng)', prop: 'workComment', span: 3 }, ], } }, } </script>
組件說(shuō)明: titleList
是組件的列配置,一個(gè)數(shù)組,元素 title 屬性是標(biāo)題,prop 指定從 data 里取值的字段,span 指定這列值跨越的行數(shù)。
prop 支持 string
,還支持函數(shù),這是實(shí)現(xiàn)自定義顯示的方式,當(dāng)這個(gè)函數(shù)很大時(shí),可提取到獨(dú)立的 js 文件中,也可以把整個(gè) titleList
提取單獨(dú)的 js 文件中。
參數(shù) h 和 data 是如何傳遞進(jìn)來(lái)的?或者 這函數(shù)在哪調(diào)用呢?
h 是 createElement
函數(shù),data
是從組件內(nèi)部的 data,和父組件傳入的 data 是同一個(gè)值。
當(dāng)普通函數(shù)的第一個(gè)參數(shù)是 h 是,它就是一個(gè) render
函數(shù)。
這種方式使用起來(lái)簡(jiǎn)單多了。
看看內(nèi)部實(shí)現(xiàn):
<template> <div class="form-table"> <ul v-if="titleList.length"> <!-- titleInfo 是經(jīng)過(guò)轉(zhuǎn)化的titleList--> <li v-for="(item, index) in titleInfo" :key="index" :style="{ width: ((item.span || 1) / titleNumPreRow) * 100 + '%' }" > <div class="form-table-title" :style="`width: ${titleWidth}px;`"> <Container v-if="typeof item.title === 'function'" :renderContainer="item.title" :data="data" /> <span v-else> {{ item.title }} </span> </div> <div class="form-table-key" :style="`width:calc(100% - ${titleWidth}px);`"> <Container v-if="typeof item.prop === 'function'" :renderContainer="item.prop" :data="data" /> <span v-else> {{ ![null, void 0].includes(data[item.prop] && data[item.prop]) || '' }} </span> </div> </li> </ul> <div v-else class="form-table-no-data">暫無(wú)數(shù)據(jù)</div> </div> </template> <script> import Container from './container.js' export default { name: 'FormTable', components: { Container, }, props: { titleWidth: { type: Number, default: 120, }, titleNumPreRow: { type: Number, default: 3, validator: value => { const validate = [1, 2, 3, 4, 5, 6].includes(value) if (!validate) { console.error('titleNumPreRow 表示一行有標(biāo)題字段對(duì),只能時(shí) 1 -- 6 的偶數(shù),默認(rèn) 3') } return validate }, }, titleList: { type: Array, default: () => { return [] }, validator: value => { const validate = value.every(item => { const { title, prop } = item return title && prop }) if (!validate) { console.log('傳入的 titleList 屬性的元素必須包含 title 和 prop 屬性') } return validate }, }, data: { type: Object, default: () => { return {} }, }, }, } </script> <!-- 樣式不是關(guān)鍵,省略 -->
實(shí)現(xiàn)自定義顯示的方式,沒(méi)有使用動(dòng)態(tài)插槽,而是用一個(gè)函數(shù)組件Container
,該組件接收一個(gè) render
函數(shù)作為 prop
。
export default { name: 'Container', functional: true, render(h, { props }) { return props.renderContainer(h, props.data) }, }
在 Container
內(nèi)部調(diào)用 titleList
傳入的函數(shù)。
總結(jié):
- 封裝組件時(shí)優(yōu)先考慮數(shù)據(jù)驅(qū)動(dòng)
- 普通函數(shù)的第一個(gè)參數(shù)是 h,就是渲染函數(shù)
- 可能有一些人不習(xí)慣寫(xiě) JSX, 可兼容兩種寫(xiě)法
到此這篇關(guān)于使用 render 函數(shù)封裝高擴(kuò)展的組件的文章就介紹到這了,更多相關(guān) render 函數(shù)封裝高擴(kuò)展組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript深拷貝方法structuredClone使用
這篇文章主要為大家介紹了JavaScript深拷貝方法structuredClone使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02前端通過(guò)JavaScript創(chuàng)建修改CAD圖形詳情
這篇文章介紹JavaScript創(chuàng)建修改CAD圖形,創(chuàng)建修改CAD圖形,一般是基于AutoCAD進(jìn)行二次開(kāi)發(fā),ObjectARX是AutoDesk公司針對(duì)AutoCAD平臺(tái)上的二次開(kāi)發(fā)而推出的一個(gè)開(kāi)發(fā)軟件包,它提供了以C++為基礎(chǔ)的面向?qū)ο蟮拈_(kāi)發(fā)環(huán)境及應(yīng)用程序接口,能真正快速的訪問(wèn)AutoCAD圖形數(shù)據(jù)庫(kù)2021-10-10js前端架構(gòu)Git?commit提交規(guī)范
這篇文章主要為大家介紹了前端架構(gòu)Git?commit提交規(guī)范示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07TypeScript順時(shí)針打印矩陣實(shí)現(xiàn)實(shí)例詳解
這篇文章主要為大家介紹了TypeScript順時(shí)針打印矩陣實(shí)現(xiàn)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09umi插件開(kāi)發(fā)仿dumi項(xiàng)目加載markdown文件實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了umi插件開(kāi)發(fā)仿dumi項(xiàng)目加載markdown文件實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01JavaScript前端分頁(yè)實(shí)現(xiàn)示例
這篇文章主要為大家介紹了JavaScript前端分頁(yè)實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07AngularJS 表達(dá)式詳細(xì)講解及實(shí)例代碼
本文主要介紹AngularJS 表達(dá)式,這里對(duì)AngularJS 表達(dá)式詳細(xì)介紹和實(shí)例代碼,有需要的小伙伴可以參考下2016-07-07微信小程序本地緩存數(shù)據(jù)增刪改查實(shí)例詳解
這篇文章主要介紹了微信小程序本地緩存數(shù)據(jù)增刪改查實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05