背景介紹
透傳是一個通訊層面的概念,指的是在通訊中不管傳輸的業務內容如何,只負責將傳輸的內容由源地址傳輸到目的地址,而不對業務數據內容做任何改變。
其實透傳這個概念,我最早是從上面一個領導那里聽到的,由于他是電氣工程師出身,而硬件通訊這塊用到透傳還是挺多的。
對于透傳,我感覺有那么一點熟悉感,仔細想想后我發現,其實我們前端也一直在使用透傳,特別是在做基礎封裝時。
透傳在前端的應用
今天就用一個Vue基礎組件封裝的過程為例,來簡單聊聊什么是透傳。
相信不少前端er做項目都會用到組件庫,是ElementUI還是Ant Design,這都不重要。然后我們又希望在第三方組件庫的基礎上再做一點點定制。
舉個例子,el-button有個屬性是size,用于控制按鈕組件的尺寸。
屬性 | 說明 | 類型 | 可選值 | 默認值 |
---|---|---|---|---|
size | 尺寸 | String | medium / small / mini | - |
可以看到,默認size是比較大的。然而我們設計師基于組件庫出自己的設計方案時,其實選擇的默認按鈕尺寸可能恰好對應ElButton的medium尺寸,或者是其他值。這樣一來,如果我不對el-button做封裝,每個使用el-button的地方都要多寫一個屬性size,類似于這樣:
- // pageA.vue
- <el-button size="medium">按鈕1</el-button>
- <el-button size="medium">按鈕2</el-button>
- // pageB.vue
- <el-button size="medium">按鈕3</el-button>
- <el-button size="medium">按鈕4</el-button>
很明顯,每使用一次el-button,我就要寫一個size屬性,好煩啊!
是的,確實很煩,那么怎么解決呢?答案是提供一個編程接口,去改變組件的默認值。有這方面考慮的組件設計者一般會提供一個設置默認值的接口,比如xxx.setDefault(options)。
那么ElementUI和Ant Design有沒有提供這樣的能力呢?據我觀察好像是沒有,其實主要是因為Vue沒有一個方便的途徑去修改prop的default屬性。但是沒有方便的途徑并不代表沒有途徑...
由于本文的主題是透傳,所以就不說那個途徑(或者說方法)了,有點跑偏了。
網友小王說:“好,那就硬上,封裝一個組件!”
好的,馬上安排!基本思路是封裝一個自定義組件,組件里面再調用el-button,并且強行給el-button安排上默認屬性size="medium"。
- <template>
- <el-button :size="size">
- <slot />
- </el-button>
- </template>
- <script>
- export default {
- name: "MyButton",
- props: {
- size: {
- type: String,
- default: 'medium'
- }
- }
- }
- </script>
聰明的讀者一看就發現了,這個組件問題很大,除了size屬性,其他屬性和事件怎么處理完全沒
小王說:“沒事,您需要什么?我給安排上!”
于是,這個組件最后就慢慢變成了:
- <template>
- <el-button
- :size="size"
- :type="type"
- :disabled="disabled"
- @click="onClick"
- >
- <slot />
- </el-button>
- </template>
- <script>
- export default {
- name: "MyButton",
- props: {
- size: {
- type: String,
- default: "medium",
- },
- type: {
- type: String,
- default: "primary",
- },
- disabled: Boolean,
- },
- methods: {
- onClick() {
- this.$emit("click");
- },
- },
- };
- </script>
看起來有點糟心,這組件甚至會更冗余,更復雜,因為我這里只加了3個prop和1個event。對于稍微復雜一點的組件來說,prop加上event一共幾十個是隨隨便便的吧!你適配得過來嗎?而且,不少人還有代碼潔癖吧,這簡直受不了!
淡定淡定!這當然是有辦法解決的。強如框架的設計者尤小右自然早已想到了這個場景,所以你應該在Vue官網文檔中關注到inheritAttrs[1]。
如何理解inheritAttrs(默認值為true)這個選項呢?我們知道,一個組件如果要接受父組件傳來的屬性,是需要先在props里面預定義好的。比如前面的例子,我在MyButton預定義了3個屬性,分別是size,type,disabled,意思是MyButton這里只接受3個prop。
那么假設父組件傳了4個或者更多prop過來呢,會怎么樣?看下面這個例子:
- <template>
- <my-button
- type="success"
- disabled
- round
- native-type="submit"
- >測試</my-button>
- </template>
實際上,round和autofocus都不是MyButton組件支持的prop,所以反映到HTML上是這么一個效果:
作為使用者,我們應該是希望round和native-type="submit"能夠傳到el-button,產生應有的效果。然而,round和native-type="submit"僅僅是掛在了根元素的attribute上,并沒有真正起到應有的作用!
PS:舉個例子,round屬性作用到el-button能讓button帶一個is-round的class,從而產生圓角效果!
也就是說,inheritAttrs的作用是:使那些沒有在props中定義的屬性,直接以attribute的形式作用在組件的根元素上!
那么round和native-type="submit"如何透傳下去呢?
首先,不能讓那些未被props標識的屬性直接落到根元素上,所以需要設置inheritAttrs為false。
然后,要獲取到那些未被props標識的屬性,并直接綁定到el-button。恰好,Vue提供了$attrs[2]用于獲取這些屬性,而v-bind本身就能綁定一個對象,這是容易被我們忽略的!
處理完屬性透傳,接下來我們還要處理事件,類似于$attrs,$listeners也能把父組件中對子組件的事件監聽全部拿到,這樣我們就能用一個v-on把這些來自于父組件的事件監聽傳遞到下一級組件。
看圖可能會更好理解!
相當于MyButton是一個不賺差價的中間商,直接透傳消息!直觀上看,組件代碼量有一個明顯的減少,更重要的是擴展性和可維護性變得更強!
- <template>
- <el-button
- v-bind="customizedAttrs"
- v-on="$listeners"
- >
- <slot />
- </el-button>
- </template>
- <script>
- export default {
- name: "MyButton",inheritAttrs:false,
- props: {
- size: {
- type: String,
- default: "medium",
- },
- },
- computed: {
- customizedAttrs() {
- return {
- size: "medium",
- // 支持傳過來的size覆蓋默認的size
- ...this.$attrs,
- };
- },
- },
- };
- </script>
對于調用者來說,使用體驗是完全沒有被影響的,他的感覺就好像仍然在直接使用el-button,屬性傳遞和事件監聽的使用體驗都沒有任何變化!
- <template>
- <my-button
- type="success"
- round
- autofocus
- @click="handleClick"
- />
- </template>
總結
結合inheritAttrs,v-bind以及v-on,我們就實現了一個支持透傳的基礎組件!本文是以Button組件為例,做的關于透傳的入門介紹。實際上,透傳的應用范圍遠遠不止Button組件,利用透傳的技巧,我們能做更多漂亮的事情!現在,你的代碼潔癖還好嗎?
原文地址:https://mp.weixin.qq.com/s/F7PkcVhN15JB6AaCap7QgQ