Vue3 封裝第三方元件(一)做一個合格的傳聲筒

金色海洋(jyk)發表於2021-04-17

各種UI庫的功能都是非常強大的,尤其對於我這種不會 css 的人來說,就更是幫了大忙了。

只是嘛,如果再封裝一下的話,那麼用起來就會更方便了。

那麼如何封裝呢?

封裝三要素 —— 屬性、插槽、事件、方法

可以封裝,但是原生UI庫提供的強大功能不能給封裝沒了吧,吃了回扣可是不好滴。
那麼如何做到不遺漏呢?先做一個合格的傳聲筒。

傳遞屬性

先看看 el-input 提供的屬性:

el-input的屬性

太長了,這裡只截了一半。
這麼多的屬性,如果一個一個都弄到 props 裡面,然後再一個一個繫結上去,這就太麻煩了。

我們可以分成兩部分,重要的屬性做到 props 裡面,其他的可以放到 $attrs 裡面。

定義一個簡單的元件

模板

<template>
  <div>
    <el-input
      v-model="value" // 不能直接幫的屬性
      v-bind="$attrs"  // 繫結其他屬性。
    >
    </el-input> 
  </div>
</template>

程式碼

export default {
  name: 'test-text',
  inheritAttrs: false,
  props: {
    modelValue: [String, Number]
  },
  setup(props, ctx) {
    const value = debounceRef(props, ctx.emit)

    return {
      value
    }
  }
}

父元件的呼叫程式碼:
模板

<inputtext
    v-model="value"
    v-bind="attrs"
  >
  </inputtext>

程式碼

const value = ref('222')

const attrs = reactive({
  maxlength: 10,
  'show-word-limit': true,
  clearable: true
})

這裡 modelValue 就是 props ,maxlength、show-word-limit、clearable 就變成了 $attrs 。

然後要看 el-input 是否是根元素,如果是跟元素的話,那麼會自動繫結上,不需要我們手動寫 v-bind="$attrs"

如果像上面的例子不是根元素的話,需要手動寫 v-bind="$attrs"

inheritAttrs

這個是指定元件是否自動繫結 $attrs 。
預設是 true,會自動把 $attrs 繫結到根元素上面,不管根元素是啥。
這裡設定為 false,那麼$attrs 就不會自動繫結到 div 上面了。

插槽

這個稍微複雜一點,插槽本來就有一點繞,官網的介紹又比較含混。

我們可以找到 $slots 這個東東,但是官網的介紹(https://www.vue3js.cn/docs/zh/api/instance-properties.html#slots )卻是 使用 h,這個就……

不過想要傳遞插槽,還是需要這個。

我們先看看 el-input 的插槽的使用。

  <el-input
    placeholder="請輸入內容"
    v-model="input3"
    class="input-with-select"
  >
    <template #prepend>
      <el-select v-model="select" placeholder="請選擇">
        <el-option label="餐廳名" value="1"></el-option>
        <el-option label="訂單號" value="2"></el-option>
        <el-option label="使用者電話" value="3"></el-option>
      </el-select>
    </template>
    <template #append>
      <el-button icon="el-icon-search"></el-button>
    </template>
  </el-input>

那麼想要傳遞插槽的話,是不是可以這樣?

   <!--傳遞插槽-->
    <template  v-slot:prepend> // 給遞給el-input 的插槽
      <slot name="prepend"></slot> // 接收父元件傳遞進來的插槽
    </template> 

測試可以。

那麼總不會一個一個寫吧,這也太麻煩了。如果能夠for就好了。

等等, for?那麼我們是不是可以這樣。

  <!--傳遞插槽-->
  <template 
    v-for="(item, key, index) in $slots"
      :key="index"
      v-slot:[key]
  >
      <slot :name="key"></slot>
  </template>  

測試可以。

完整程式碼

    <el-input
      ref="refInput"
      v-model="value"
      v-bind="$attrs"
    >
      <!--傳遞插槽-->
      <template 
        v-for="(item, key, index) in $slots"
        :key="index"
        v-slot:[key]
      >
        <slot :name="key"></slot>
      </template> 
    </el-input> 

傳遞事件

這個就簡單了,啥都不用做,自動就傳遞出去了。el-input 是否是跟元素都可以。
測試一下:

 <inputtext
    ref="refInput"
    v-model="value"
    v-bind="props"
    @clear="clear"
    @my-change="myChange"
  >
  • clear 是 el-input 提供的事件,外部可以直接得到這個事件,元件內部不用做操作。
  • my-change 是自定義的事件。

方法

一直都忽略了,還有方法這個事,因為基本沒用過。

使用方法嘛,就需要使用 ref,這個此 ref 非彼 ref,說不清了,還是寫程式碼吧。

直接使用的方法

直接使用UI庫元件的方法,比如 el-input 的 提供的 select:

el-input的方法

<el-input
  ref="refInput" // 注意這裡的 ref 
  v-model="value"
  v-bind="$attrs" >
</el-input> 

ref 的寫法,不要加冒號。

const refInput = ref(null) // 先放一個null
onMounted(() => { // 然後在 onMounted 裡面才能得到值。
  console.log('refinput', refInput) // 看看啥樣。
  refInput.value.select() // 呼叫方法,文字框的內容會被選中
})

先定義一個 ref,然後交給模板裡的 ref,好像有點繞,這裡必須使用 ref,reactive是不行滴。

在渲染後才能生效,也就是說必須在 onMounted 裡面才能得到值,我們看看列印結果:(太長只能擷取一部分)

ref

很長吧。

父元件裡面怎麼用方法

<inputtext
    ref="refInput"
    v-model="value">
</el-input> 
// 測試方法
const refInput = ref(null)

onMounted(() => {
  console.log('refinput', refInput)
  // refInput.value.$refs.refInput.select()
  refInput.value.refInput.select()
})

父元件裡面的用法是一樣的,只是需要再套一層,才能拿到自定義元件內部的UI庫元件。

看看結構:
父元件呼叫方法一

太長了,還在下面。

父元件呼叫方法二

這個就比較近了。

話說為啥弄得這麼多屬性和方法事件呀?

父元件呼叫子元件內部的方法

上面那種方式,還可以讓父元件呼叫子元件內部定義的方法,比如內部定義一個

   const setInput = () => {
      value.value = new Date()
    }

父元件可以這樣呼叫

refInput.value.setInput()

總結

其實事件和方法,並沒有封裝,而是直接就可以使用的。
這是 element-plus 測試的結果,其他UI庫沒有測試。

插槽需要寫一個 v-for 就可以實現傳遞,而且是通用的程式碼。
屬性 就需要規劃一下了,看設計要求,哪些放在 props裡面,哪些放在attrs 裡面。

相關文章