成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Vue如何二次封裝一個(gè)高頻可復(fù)用的組件

開(kāi)發(fā) 開(kāi)發(fā)工具
在我們的業(yè)務(wù)里,我們通常會(huì)二次封裝一些高頻業(yè)務(wù)組件,比如彈框,抽屜,表單等這些業(yè)務(wù)組件,為什么要二次封裝?

在我們的業(yè)務(wù)里,我們通常會(huì)二次封裝一些高頻業(yè)務(wù)組件,比如彈框,抽屜,表單等這些業(yè)務(wù)組件,為什么要二次封裝?我們所有人心里的答案肯定是,同樣類似的代碼太多了,我想復(fù)用組件,或者原有組件可能達(dá)不到我想要的效果,我想基于原有組件自定義一些自己的接口,那么此時(shí)就需要二次封裝了。二次封裝雖好,但同時(shí)也會(huì)帶來(lái)一定的心智負(fù)擔(dān),因?yàn)槎畏庋b的組件可能會(huì)變得不那么純粹。

本文是一篇筆者關(guān)于二次封裝組件的思考,希望看完在項(xiàng)目中有所思考和幫助。

在內(nèi)容開(kāi)始之前,本文主要從以下幾個(gè)方向去思考:

1、二次組件必須繼承原有組件的所有特性。

2、二次組件名必須見(jiàn)名知意。

3、自定義暴露出來(lái)的接口越簡(jiǎn)單越好。

4、留有自定義插槽,讓用戶可以自己選擇。

5、封裝二次的組件,能根據(jù)schame數(shù)據(jù)配置,讓組件更通用。

繼承原有組件接口

在之前的項(xiàng)目例子中,我們以一個(gè)彈框組件為例:

我們看下在業(yè)務(wù)中一般是怎么寫的。

<template>
<div class="list-app">
<div><a href="javascript:void(0)" @click="handleToHello">to hello</a></div>
...
<list-modal
title="編輯"
width="50%"
v-model="formParams"
:visible.sync="dialogVisible"
@refresh="featchList"
></list-modal>
</div>
</template>
<script>
import { sourceDataMock } from '@/mock';
import ListModal from './ListModal';


export default {
name: 'list',
components: {
ListModal,
},
...
};
</script>

我們?cè)倮^續(xù)看下list-modal這個(gè)組件。

<!--ListModal.vue-->
<template>
<el-dialog
:visible.sync="currentVisible"
width="30%"
v-bind="$attrs"
>
<el-form label-position="left" label-width="80px" :model="formParams">
<el-form-item label="日期">
<el-input v-model="formParams.date"></el-input>
</el-form-item>
<el-form-item label="名稱">
<el-input v-model="formParams.name"></el-input>
</el-form-item>
<el-form-item label="地址">
<el-input v-model="formParams.address"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeModal">取 消</el-button>
<el-button type="primary" @click="handleSure">確 定</el-button>
</span>
</el-dialog>
</template>

我們會(huì)發(fā)現(xiàn),這個(gè)list-modal業(yè)務(wù)組件只是包了一層,當(dāng)我們使用v-bind="$attrs"時(shí),vue提供的這個(gè)api會(huì)將父組件所有的props繼承,官方給了一大段解釋。

  • $attrs

包含了父作用域中不作為 prop 被識(shí)別 (且獲取) 的 attribute 綁定 (class 和 style 除外)。當(dāng)一個(gè)組件沒(méi)有聲明任何 prop 時(shí),這里會(huì)包含所有父作用域的綁定 (class 和 style 除外),并且可以通過(guò) v-bind="$attrs" 傳入內(nèi)部組件——在創(chuàng)建高級(jí)別的組件時(shí)非常有用。

首先我們思考為什么要用這個(gè)$attrs?上面一段話的意思是,父組件class與style會(huì)排除。

圖片

我們從頁(yè)面上可以看出title與width都是父組件傳過(guò)來(lái)的,但是我們發(fā)現(xiàn),實(shí)際上這兩個(gè)外部看似自己傳入的props也是el-dialog的props,所以說(shuō)我們必須要保持自己二次封裝的組件也有el-dialog所有能力,所以此時(shí)v-bind='$attrs'就可以做到了。

  • $listeners

包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監(jiān)聽(tīng)器。它可以通過(guò) v-notallow="$listeners" 傳入內(nèi)部組件——在創(chuàng)建更高層次的組件時(shí)非常有用。

在以上的$attrs我們是將父級(jí)的所有的props都拿到了,但是自定義事件呢,所以才有的了$listeners。

所以你在父組件寫了一個(gè)el-dialog的自定義事件想要生效,那么必須要在子組件綁定$listeners。

<!--list/ListModal.vue-->
<el-dialog
:visible.sync="currentVisible"
width="30%"
v-bind="$attrs"
v-notallow="$listeners"
>
...
</el-dialog>

正常來(lái)說(shuō)一個(gè)高階二次組件必須要有v-bind="$attrs"與v-notallow="$listeners"。

另外我們自己封裝的二次組件里有v-model='formParams'。

這個(gè)formParams就是我們彈框內(nèi)部表單的使用內(nèi)容。

v-model

關(guān)于v-model實(shí)際上官方解釋就是用在組件或者表單上創(chuàng)建雙向綁定,如果把v-model看成是一個(gè)內(nèi)部提供的一個(gè)語(yǔ)法糖,那么它可以拆解成:value="value"與:input=“handleInput”,v-model不僅僅是可以作用在表單元素上,并且還可以作用在組件上,同時(shí)也提供了一個(gè)model的接口,提供自定義修改事件名稱。

<script>
export default {
name: 'list-modal',
model: {
prop: 'formParams',
event: 'change',
},
props: {
visible: {
type: Boolean,
default: false,
},
formParams: {
type: Object,
},
},
data() {
return {
currentVisible: false,
};
},
watch: {
visible(bool) {
this.currentVisible = bool;
},
currentVisible(bool) {
this.$emit('update:visible', bool);
},
}
};
</script>

以上代碼就自定義了model的event,prop就是formParams,同時(shí)props上必須有引入formParams。

不知道你有沒(méi)有好奇,為啥我data中定義了一個(gè)currentVisible,而且watch了visible與currentVisible,使用currentVisible時(shí),這里是有一個(gè)坑,因?yàn)閺椏虻膇con關(guān)閉操作不會(huì)觸發(fā)最外層事件,也就是你點(diǎn)擊右上角的關(guān)閉操作后,當(dāng)你再次打開(kāi)時(shí),此時(shí),就打不開(kāi)了,所以就沒(méi)直接用visible了,我們需要另一個(gè)變量,然后去watch最終達(dá)到我們需要的效果。

在這里有人會(huì)奇怪,傳入子組件的formParams直接在表單上使用了,嘿,這樣不是直接修改props嗎,但實(shí)際上控制臺(tái)并不會(huì)報(bào)錯(cuò),如果你父組件傳入的是一個(gè)基礎(chǔ)數(shù)據(jù)類型,你在子組件里修改是會(huì)直接警告你不能修改的,但是你傳入的是一個(gè)對(duì)象,你此時(shí)修改的是對(duì)象屬性值,并沒(méi)有修改原對(duì)象,所以一個(gè)非基礎(chǔ)數(shù)據(jù)類型數(shù)據(jù),修改內(nèi)部值時(shí),是不會(huì)警告的,這樣做也是ok的。

插槽

在這個(gè)彈框中的確認(rèn)和取消操作是用插槽slot="footer"去顯示的,如果你想自定義插槽,那么你可以通過(guò)具名插槽進(jìn)行兼容處理。

<el-dialog
:visible.sync="currentVisible"
width="30%"
v-bind="$attrs"
v-on="$listeners"
>
...
<template v-if="$slots.footer">
<slot name="footer" />
</template>
<span v-else slot="footer" class="dialog-footer">
<el-button @click="closeModal">取 消</el-button>
<el-button type="primary" @click="handleSure">確 定</el-button>
</span>
</el-dialog>

在我們的業(yè)務(wù)中有大量這樣的XXXModal彈框,如果我們只是這樣包了一層,那么我們只是完成了組件的基本使用,也是符合我們常規(guī)業(yè)務(wù)需求,但是你會(huì)發(fā)現(xiàn),我們絕大部份業(yè)務(wù)里的彈框內(nèi)容都是表單,所以我能不能通過(guò)可配置的schame數(shù)據(jù)去配置出來(lái)呢?

組件更抽象

我們?cè)赾omponents下新建了一個(gè)form-modal組件,并注冊(cè)成全局組件,我的目標(biāo)是把彈框的內(nèi)容區(qū)域做成可配置化,這樣我只需要用配置數(shù)據(jù)就可以渲染出對(duì)應(yīng)的內(nèi)容。

<!--src/components/form-modal/view/index.vue-->
<template>
<div class="form-modal">
<el-dialog :visible.sync="currentVisible" v-bind="$attrs" v-on="$listeners">
<el-form v-bind="formConfig.formAttrs" :model="formParams">
<div v-for="(item, index) in formConfig.fields" :key="index">
<el-form-item :label="item.label">
<!--自定義插槽-->
<template v-if="item.slot">
<slot :name="item.slot" :row="{ ...item, formParams, index }" />
</template>
<!--文本or文本域-->
<template v-else-if="['text', 'textarea'].includes(item.type)">
<el-input
:type="item.type"
v-bind="item.attrs || {}"
v-model="formParams[item.key]"
></el-input>
</template>
<!--下拉框-->
<template v-else-if="item.type === 'select'">
<el-select v-bind="item.attrs" v-model="formParams[item.key]">
<el-option
v-for="(sitem, index) in item.options.data"
:key="index"
:label="sitem[item.options.extraProps.label]"
:value="sitem[item.options.extraProps.value]"
>
</el-option>
</el-select>
</template>
</el-form-item>
</div>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeModal">取 消</el-button>
<el-button type="primary" @click="handleSure">確 定</el-button>
</span>
</el-dialog>
</div>
</template>

全局注冊(cè)

// src/components/index.js
import Vue from 'vue';
import FormModal from './form-modal';
const custCompoment = {
FormModal,
};
export const installCustComponent = () => {
Object.keys(custCompoment).forEach((key) => {
Vue.component(key, custCompoment[key]);
});
};

main.js

// main.js
import { installCustComponent } from '@/components';
installCustComponent();
...

我們發(fā)現(xiàn)在模版里面有不少添加條件,實(shí)際上,這些條件主要根據(jù)你業(yè)務(wù)需要而定,除了模版方式,插槽,我們也可以預(yù)留一個(gè)自定義formater的接口,像下面這樣。

<!--src/components/form-modal/view/index.vue-->
<div v-for="(item, index) in formConfig.fields" :key="index">
<el-form-item :label="item.label">
<!--自定義render-->
<template v-if="item.formater">
<component
:is="'renderComponent'"
:value="formParams[item.key]"
:input="e => formParams[item.key] = e"
v-bind="{ ...item }"
></component>
</template>
<!--自定義插槽-->
<template v-else-if="item.slot">
<slot :name="item.slot" :row="{ ...item, formParams, index }" />
</template>
<!--文本or文本域-->
...
</el-form-item>
</div>

那么此時(shí)你會(huì)發(fā)現(xiàn)有一個(gè)renderComponent這樣的自定義組件,我們必須引入進(jìn)來(lái)。

/* src/components/form-modal/view/render.js*/
export default {
functional: true,
props: ['value'],
render(h, ctx) {
const { formater, attrs, input: handleInput } = ctx.data.attrs;
return formater(h, {
attrs: {
...attrs,
value: ctx.props.value,
},
on: {
input(e) {
handleInput(e);
},
},
});
},
};

在form-modal/view/index.vue中我們必須引入,所以模版中就可以使用了。

<script>
// src/components/form-modal/view/index.vue
import renderComponent from './render';
export default {
name: 'form-modal',
model: {
prop: 'formParams',
event: 'change',
},
components: {
renderComponent,
},
props: {
visible: {
type: Boolean,
default: false,
},
formParams: {
type: Object,
},
formConfig: {
type: Object,
},
},
...
</script>

我們?cè)倏聪挛覀冎皹I(yè)務(wù)彈框與schame再次抽象后的兩個(gè)組件,其實(shí)第二個(gè)全局組件就多了一個(gè)formConfig,我們統(tǒng)一把內(nèi)容抽離了出去,實(shí)際上呢,我們的form-modal就變得更加通用,我們只需要關(guān)注formConfig這份配置數(shù)據(jù)就行。

/* eslint-disable func-names */
<template>
<div class="list-app">
...
<list-modal
title="編輯"
width="50%"
class="list-modal"
style="border: 1px solid transparent"
v-model="formParams"
:visible.sync="dialogVisible"
@refresh="featchList"
@close="handleClose"
>
<div slot="footer">確定</div>
</list-modal>
<form-modal
title="編輯"
width="50%"
class="list-modal"
style="border: 1px solid transparent"
v-model="formParams"
:formConfig="formConfig"
:visible.sync="dialogVisible2"
@refresh="featchList"
@close="handleClose"
>
<template slot-scope="{ row }" slot="number">
<el-input
:type="row.type"
v-bind="row.attrs || {}"
v-model="row.formParams[row.slot]"
></el-input>
</template>
</form-modal>
</div>
</template>


<script>
import { sourceDataMock } from '@/mock';
import ListModal from './ListModal';
export default {
name: 'list',
components: {
ListModal,
},
data() {
return {
...
tableData: [],
dialogVisible: false,
dialogVisible2: false,
formParams: {
date: '',
name: '',
address: '',
number: '1',
scholl: '公眾號(hào):Web技術(shù)學(xué)苑',
},
};
},
computed: {
formConfig() {
return {
formAttrs: {
labelWidth: '80px',
labelPosition: 'left',
},
fields: [
{
type: 'text',
key: 'date',
label: '日期',
attrs: {
placeholder: '請(qǐng)?zhí)顚懭掌?,
},
},
{
type: 'text',
key: 'name',
label: '名稱',
attrs: {
placeholder: '請(qǐng)?zhí)顚懨Q',
},
},
{
type: 'select',
key: 'address',
label: '地址',
attrs: {
placeholder: '請(qǐng)選擇地址',
style: {
width: '100%',
},
},
options: {
data: this.tableData,
extraProps: {
value: 'address',
label: 'address',
},
},
},
{
type: 'text',
slot: 'number',
label: '編號(hào)',
attrs: {
placeholder: '請(qǐng)輸入編號(hào)',
},
},
{
type: 'text',
key: 'scholl',
label: '畢業(yè)學(xué)校',
attrs: {
placeholder: '請(qǐng)輸入畢業(yè)學(xué)校',
},
formater: (h, props) =>
h('el-input', {
...props,
}),
},
],
};
},
},
};
</script>


<style scoped>
.list-app .el-form {
text-align: left;
}
</style>

看下最終的結(jié)果:

圖片

在我們自定義一個(gè)formater的接口,我們注意到,實(shí)際上這里有用vue的純函數(shù)組件,我們注意到在render.js中我們是申明了functional: true,這里會(huì)有巨坑,如果是一個(gè)函數(shù)組件,在render函數(shù)中是獲取不到this的,只能通過(guò)第二個(gè)ctx參數(shù)獲取父組件傳入的props信息。

/* eslint-disable no-param-reassign */
export default {
functional: true,
props: ['value'],
render(h, ctx) {
// console.log(this, '---'); // 會(huì)是null,只能通過(guò)第二個(gè)參數(shù)ctx拿對(duì)應(yīng)參數(shù)
const { formater, attrs, input: handleInput } = ctx.data.attrs;
return formater(h, {
attrs: {
...attrs,
value: ctx.props.value,
},
on: {
input(e) {
handleInput(e);
},
},
});
},
};

并且我們修改數(shù)據(jù),我們發(fā)現(xiàn)我們用了一個(gè)父組件傳入的一個(gè)回調(diào)函數(shù)去修改,這在react很常見(jiàn),這里我們也是通過(guò)回調(diào)方式修改數(shù)據(jù),因?yàn)関ue數(shù)據(jù)流是單向的,所以只能這種方式去修改了。

因此在業(yè)務(wù)中我們的form-modal就變得更通用,更高頻了,這樣會(huì)減少你重復(fù)勞動(dòng)的時(shí)間,你只需要關(guān)注配置接口信息就行。

但是這樣帶來(lái)的負(fù)擔(dān)是有的,如果這個(gè)form-modal耦合了太多業(yè)務(wù)邏輯,那么帶來(lái)的心智負(fù)擔(dān)是有的,當(dāng)你二次封裝的一個(gè)高頻組件,你組內(nèi)小伙伴不能像使用第三方組件庫(kù)那么快捷時(shí),說(shuō)明組件的接口設(shè)計(jì)還有提高的空間,判斷一個(gè)組件好不好用的標(biāo)準(zhǔn)就是,零負(fù)擔(dān),而且人人能改,人人都能改動(dòng),如果因?yàn)闃I(yè)務(wù)特殊,當(dāng)我們考慮二次封裝一個(gè)組件參雜很多業(yè)務(wù)邏輯判斷時(shí),那我的觀點(diǎn)是,還是不要進(jìn)行二次封裝了。

總結(jié)

以一個(gè)彈框組件為例,我們二次封裝組件到底需要注意哪些問(wèn)題,以及我們必須注意些什么,核心思想就是繼承原有組件的特性,v-bind='$attrs'與v-notallow="$listeners"是核心。

當(dāng)我們二次封裝一個(gè)組件時(shí),我們自定義的一些接口能少就少,組件名必須見(jiàn)名知意。

二次封裝的組件不僅僅只是包一層,我們可以嘗試用數(shù)據(jù)配置方式讓組件更通用,預(yù)留一些接口插槽,或者自定義formater函數(shù),不強(qiáng)制約束,讓組件靈活性拓展性更強(qiáng)些。

組件的props名字盡量不要帶來(lái)負(fù)擔(dān),最好與原有組件props保持一致。

本文code example[1]。

參考資料

[1]code example: https://github.com/maicFir/lessonNote/tree/master/vue/05-keep-alive。

責(zé)任編輯:武曉燕 來(lái)源: Web技術(shù)學(xué)苑
相關(guān)推薦

2023-04-10 08:30:30

json編輯器typescript

2022-05-13 08:46:46

jsoneditorjson編輯器

2021-05-05 11:34:09

前端開(kāi)發(fā)技術(shù)熱點(diǎn)

2024-07-30 08:59:22

2021-12-05 21:05:49

前端JSON API

2024-03-13 13:39:21

2024-04-01 11:52:46

2021-03-03 08:32:09

開(kāi)源子節(jié)點(diǎn)組件

2024-11-20 13:18:21

2025-06-16 01:00:00

2015-12-04 11:36:04

SaaS架構(gòu)設(shè)計(jì)可持續(xù)

2018-01-31 15:45:07

前端Vue.js組件

2024-11-15 10:03:43

應(yīng)用模板Vue

2022-04-12 14:00:05

元宇宙人工智能安全

2024-01-22 04:15:00

Vue3組件開(kāi)發(fā)

2022-09-20 11:00:14

Vue3滾動(dòng)組件

2021-09-03 13:54:45

雙重勒索勒索軟件攻擊

2020-10-29 15:13:55

數(shù)字科技金融行業(yè)互聯(lián)網(wǎng)

2021-12-07 06:55:17

節(jié)流函數(shù)Throttle

2015-03-10 11:21:44

JavaScript組JavaScript
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 粉嫩av| 成人亚洲 | 奇色影视 | caoporn国产精品免费公开 | 欧美日韩综合 | 中文字幕av亚洲精品一部二部 | 欧美日韩在线观看一区 | 久久大 | 99精品网 | 成人毛片在线视频 | 国产精品精品久久久 | 亚洲国产精品视频一区 | 国产精品资源在线 | 国产一在线观看 | 日本涩涩视频 | 国产日韩欧美中文字幕 | 日本免费视频 | 精精国产xxxx视频在线播放 | 啪啪精品 | 黄色av网站在线观看 | 精品国产乱码一区二区三区a | 日韩成人免费视频 | 精品日本中文字幕 | 99爱在线免费观看 | 午夜影院 | 99久久免费精品国产男女高不卡 | 国产精品爱久久久久久久 | 国产一级视频在线 | 国产乱码精品1区2区3区 | 91久久久精品国产一区二区蜜臀 | 亚洲精品视频久久 | 亚洲大片在线观看 | 天天射网站| 成人精品鲁一区一区二区 | 国产在线精品一区二区三区 | 国产免费一区二区三区免费视频 | 亚洲一区二区三区免费观看 | 成人精品一区二区 | 亚洲国产精品一区二区三区 | 99精品99| 国产高清视频在线 |