前端如何修改組件庫(kù)源碼來(lái)封裝符合自己需求的組件?
前端開(kāi)發(fā)的同學(xué)們或許會(huì)遇到這樣的問(wèn)題:產(chǎn)品中需要實(shí)現(xiàn)某項(xiàng)功能,常用的?elementui、antd?等組件庫(kù)中確實(shí)有差不多功能的組件。但實(shí)際上這些組件可能并不能滿足你的功能,或多或少都需要你去看看如何修改它才能滿足你的需求。
比如我曾遇到過(guò)?element-ui?中的「樹(shù)形控件」暴露出的參數(shù)沒(méi)有我需要的(獲取參數(shù));或者是「對(duì)話框」組件我需要給它的?body?加上上下兩條?border?等(樣式修改);還有「級(jí)聯(lián)選擇器」的多選可搜索功能:需要修改級(jí)聯(lián)看板使它保持展開(kāi),且當(dāng)子節(jié)點(diǎn)全部選中時(shí),不展示全部子節(jié)點(diǎn)?tag?而只展示它的父節(jié)點(diǎn)?tag(源碼無(wú)此功能)。
例如,我需要的功能是左二(因?yàn)槲也幌脒x項(xiàng)過(guò)多時(shí)?tag?占得位置太大),而原組件如左一。我截了兩張圖對(duì)比:
關(guān)于組件庫(kù)可能要修改的地方,我將它們分為以下五類可供參考:
樣式問(wèn)題 組件暴露的參數(shù)和方法不充分(源碼中存在) 可利用部分功能,其余功能要自己開(kāi)發(fā)封裝 2+?個(gè)組件之間的聯(lián)動(dòng),多合一完全沒(méi)有符合的組件
下面詳細(xì)說(shuō)說(shuō)這些問(wèn)題,以及如何解決這些問(wèn)題。如果有不滿足或更好的建議,歡迎指出。
1、組件樣式問(wèn)題
當(dāng)修改單個(gè)文件的樣式時(shí),以?less?為例,如果你想要修改組件的樣式,可以使用?/deep/?或?>>>?來(lái)深度選擇到你要修改的樣式(這能夠幫你省去一大串的類名)。
.dialog-wrapper?{
????/deep/.el-dialog__body{
?????border:?solid?1px?#999;
????}
}
如果你要修改全局的樣式,第一種方法,你可以在全局樣式文件中寫樣式覆蓋,引入到?main.js?中即可全局生效。如下:
import?"./assets/css/index.css";
第二,跟著組件庫(kù)提供的『自定義主題』教程修改,一般組件庫(kù)都會(huì)給出相關(guān)的教程。
2、組件暴露的參數(shù)和方法不充分
首先提出一個(gè)問(wèn)題,你如何知道組件暴露的參數(shù)和方法不充分?其實(shí)答案很簡(jiǎn)單:因?yàn)槲铱戳私M件庫(kù)的源碼。
當(dāng)我們想要獲取組件的一個(gè)參數(shù),首先是看文檔中提供了哪些?Attributes、Events、Methods。如果符合需求,直接拿來(lái)用就好。如果沒(méi)有你要的屬性和方法,請(qǐng)你先去看看源碼中提供了哪些東西沒(méi)有向外暴露出來(lái)的,但是我們能拿來(lái)用的。
舉個(gè)??,上述的「Cascader 級(jí)聯(lián)選擇器」,我想要在選中一個(gè)搜索的選項(xiàng)后不關(guān)閉看板。我在組件的?Events、Methods中沒(méi)有找到相關(guān)的方法控制看板展開(kāi),如下:

但當(dāng)我去?github?上看該組件的源碼時(shí),我發(fā)現(xiàn)?toggleDropDownVisible()?方法是控制看板展開(kāi)的。于是我在外部用?$refs?直接調(diào)用組件里的這個(gè)方法就好了。
具體調(diào)用方法如下,這樣即使方法沒(méi)有暴露出來(lái),也可以調(diào)用它內(nèi)部的方法:
????ref="cascader"?//?ref獲取組件
????placeholder="試試搜索:指南"
????:options="options"
????:props="{?multiple:?true?}"
????filterable></el-cascader
????@visible-change="$refs.cascader.toggleDropDownVisible(true)">?//?調(diào)用組件及其方法
3、可利用部分功能,其余功能要自己開(kāi)發(fā)封裝
第三類其實(shí)我們用到的已經(jīng)比較少了,畢竟現(xiàn)在的組件庫(kù)已經(jīng)非常豐富了。但是這一步引起的思考確是很重要的,多看別人的源碼有助于提高自己封裝組件的水平。
當(dāng)要用到一個(gè)組件,但從頭開(kāi)發(fā)這個(gè)組件既復(fù)雜又耗時(shí),而組件庫(kù)中這個(gè)組件需要再往上加一些功能就能為你所用時(shí),你可以考慮把組件庫(kù)的代碼拿到自己本地,修改它。
第一步你需要將組件代碼瀏覽一遍,了解它的邏輯。看看你需要加什么代碼,如果在?vue?中,使用?computed、watch,或是修改?created、mounted、methods?就能完成你的功能,那么就大膽地嘗試。
舉個(gè)??
在?element-ui?中,它提供的多選可搜索級(jí)聯(lián)組件有一個(gè)問(wèn)題:當(dāng)用戶選中全部子節(jié)點(diǎn)時(shí)不會(huì)合并為顯示父節(jié)點(diǎn)。要想完成這個(gè)功能,在經(jīng)歷過(guò)上述步驟一番探索后發(fā)現(xiàn)還是要修改源碼才能完成。于是我基于原本多選可搜索的級(jí)聯(lián)選擇器,進(jìn)行以下優(yōu)化:
默認(rèn)看到級(jí)聯(lián)看板展開(kāi),不會(huì)收起
@visible-change="blurCascader(true)"?//?可觸發(fā)展開(kāi)
mounted()?{
????this.blurCascader(true)
}
//?失焦后觸發(fā)展開(kāi)級(jí)聯(lián)看板(默認(rèn)失焦后關(guān)閉看板)
blurCascader()?{
????this.$nextTick(()?=>?{
????????this.$refs.cascader.toggleDropDownVisible(true)?//?調(diào)用組件內(nèi)部未暴露的方法
????})
},
搜索選中后展示級(jí)聯(lián)看板,并勾選搜索選中的節(jié)點(diǎn)
//?響應(yīng)選中的節(jié)點(diǎn),選中節(jié)點(diǎn)后關(guān)閉選擇看板,展示級(jí)聯(lián)看板
changecascader(e)?{
this.$refs.cascader.handleDropdownLeave()
},
當(dāng)子級(jí)節(jié)點(diǎn)全部選中后,tag只展示一個(gè)父級(jí)節(jié)點(diǎn),而不是全部子節(jié)點(diǎn)
?//?獲取所有勾選的節(jié)點(diǎn)
????getPresetTags()?{
??????const?tree?=?this.panel.menus[0]
??????const?result?=?[]
??????loop(tree)
??????//?遞歸查找選中的節(jié)點(diǎn)
??????function?loop(tree?=?[])?{
????????for?(let?i?=?0;?i???????????const?child?=?tree[i]
??????????if?(child.checked)?{????????????????//?checked?狀態(tài)表示選中
????????????result.push({?...child,?closable:?true?})
??????????}?else?if?(child.indeterminate)?{???//?indeterminate?狀態(tài)表示待定,是半選
????????????child.children?&&?loop(child.children)
??????????}
????????}
??????}
??????this.presentFormatTags?=?result?//?得到可顯示的?tag
????},
刪除節(jié)點(diǎn)
由于我修改了?tag?的展示,所以它的?deleteTag?事件也要重寫。
deleteTag(index,?tag)?{
??????let?_?=?this
??????if?(tag?&&?tag.hasChildren)?{
???????//?當(dāng)刪除的節(jié)點(diǎn)是父節(jié)點(diǎn)時(shí)
????????loop(tag.children)
????????function?loop(list)?{
??????????for?(let?i?=?0;?i?????????????if?(list[i].hasChildren)?{
??????????????loop(list[i].children)
????????????}?else?{
??????????????_.checkedValue?=?_.checkedValue.filter(n?=>?n?!==?list[i].path)
??????????????_.$emit('remove-tag',?tag)
????????????}
??????????}
????????}
??????}?else?if?(tag)?{
??????//?當(dāng)刪除的是子節(jié)點(diǎn)時(shí)
????????this.checkedValue?=?this.checkedValue.filter((n,?i)?=>?n?!==?tag.path)
????????this.$emit('remove-tag',?tag)
??????}?else?{
??????//?當(dāng)以回車鍵刪除時(shí)
????????const?temp?=?this.presentFormatTags[this.presentFormatTags.length?-?1]
????????temp?&&?this.deleteTag(null,?temp)
??????}
??????//?原本這個(gè)方法的代碼如下
??????//?const?{?checkedValue?}?=?this
??????//?const?val?=?checkedValue[index]
??????//?this.checkedValue?=?checkedValue.filter((n,?i)?=>?i?!==?index)
??????//?this.$emit('remove-tag',?val)
????}
其實(shí)修改組件庫(kù)代碼的過(guò)程并不難,主要是看懂它的邏輯,以及其中哪些東西是你可以用的。
上述組件的源碼可以在 GitHub 查看。
4、2+?個(gè)組件之間的聯(lián)動(dòng),多合一
到這一步,其實(shí)你已經(jīng)翻越最難的大山了!而這里要說(shuō)的多個(gè)組件之間的聯(lián)動(dòng)其實(shí)已經(jīng)處于優(yōu)化用戶體驗(yàn)的道路上了。思考一下,什么時(shí)候會(huì)用到多個(gè)組件之間的聯(lián)動(dòng)呢?
其實(shí)場(chǎng)景有很多,例如將「Form 表單」、「Table 表格」和「Pagination 分頁(yè)」結(jié)合起來(lái),封裝成一個(gè)組件,這樣在多表格的項(xiàng)目中直接使用就好了;
將?table?和?pagination?放到一個(gè)組件中:
"pug">
div
??.el-table
????template(v-for="(item,?index)?in?columns")
??????el-table-column(
????????:prop="item.prop"
????????:key="index"
????????:label="item.label")
??el-pagination.pg-wrapper(
????layout="total,?sizes,?prev,?pager,?next,?jumper"
????@size-change="handleSizeChange"
????@current-change="handleCurrentChange"
????:current-page="currentPage"
????:page-sizes="[10,?20,?50,?100]"
????:page-size="pagesize"
????:total="total")
需要傳入的參數(shù)如下:列信息?columns、單頁(yè)數(shù)據(jù)量?pagesize、當(dāng)前頁(yè)碼?currentPage、表格數(shù)據(jù)?tableData、數(shù)據(jù)總數(shù)?total、表單查詢的參數(shù)?query?等。
props:?{
??//?列信息
??columns:?{
????type:?Array,
????default:?[],
??}
??//?單頁(yè)數(shù)據(jù)量
??pagesize:?{
????type:?Number,
????default:?10,
??},
??//?當(dāng)前頁(yè)碼
??currentPage:?{
????type:?Number,
????default:?1,
??},
??//?表格數(shù)據(jù)
??tableData:?{
????type:?Array,
????default:?[],
??},
??//?數(shù)據(jù)總數(shù)
??total:?{
????type:?Number,
????default:?1000,
??},
??//?獲取數(shù)據(jù)的接口
??fetch:{
?????type:?function,
?????default:()?=>?{}
??},
??//?表單查詢的參數(shù)
??query:{
????type:?Object,
????default:?()?=>?{}
??}
},
methods:?{
??//?改變當(dāng)前頁(yè)碼?currentPage?時(shí)觸發(fā)
??handleCurrentChange:?function?(currentPage)?{
????this.$emit('handleChange',?this.pagesize,?currentPage)
????this.fetch(this.query)
??},
??//?改變當(dāng)前頁(yè)?pageSize?時(shí)觸發(fā)
??handleSizeChange:?function?(pageSize)?{
????this.$emit('handleChange',?pageSize,?this.currentPage)
????this.fetch(this.query)
??}
}
使用時(shí)我們只需要傳以上的參數(shù)就可以直接調(diào)用這兩個(gè)組件了。
還有「表格中行選中狀態(tài)數(shù)據(jù)」,與「展示數(shù)據(jù)」之間的聯(lián)動(dòng)等等,可發(fā)揮之處有很多。將他們封裝后可以大大減輕重復(fù)的工作量,特別是像后臺(tái)管理類的項(xiàng)目,頁(yè)面間相似度很高的,尤其適合這種方法。
5、完全沒(méi)有符合的組件
如果你要的組件,外部的組件庫(kù)中都沒(méi)有提供,那就自己動(dòng)手封裝一個(gè)。盡可能將你的組件變得通用,兼容。嘗試想一想你的組件是否在其他情況下也能用。另外也可以多看看別人是如何封裝組件的,這有助于你自己開(kāi)發(fā)。
如果你可以將你在前端開(kāi)發(fā)道路上自己封裝的組件一個(gè)個(gè)收集起來(lái),大概率可以方便你以后相同場(chǎng)景下直接復(fù)用,也有助于你的代碼解耦。
總結(jié)
如果你遇到了組件庫(kù)中的組件不合適的,先考慮看看是否能利用它的方法或?qū)傩赃_(dá)到效果,再看看能否修改它的代碼達(dá)成目的。如果最后實(shí)在不行,那么就自己動(dòng)手造輪子吧!自己造的輪子記得記下來(lái),沒(méi)準(zhǔn)以后就能用上!
最后,Vue Demo Collection 這個(gè)項(xiàng)目,是我在開(kāi)發(fā)過(guò)程中遇到的通用Vue?組件的?demo?收集,包含了?Vue/CSS/Echarts?等一些可以復(fù)用的組件 ??,基本上我認(rèn)為可以復(fù)用的組件和代碼片段我都會(huì)記錄在這,方便自己的回顧和使用,也算是個(gè)人成長(zhǎng)的記錄。
作者:Huup_We
https://juejin.cn/post/6917771825808146446
回復(fù) 資料包領(lǐng)取我整理的進(jìn)階資料包回復(fù) 加群,加入前端進(jìn)階群console.log("點(diǎn)贊===再看===快樂(lè)")Bug離我更遠(yuǎn)了,快樂(lè)離我更近了
