從vue組件三大核心概念出發,寫好一個組件【理論篇】

一個適用性良好的組件,一種是可配置項不少,另外一種就是容易覆寫,從而擴展功能css

Vue 組件的 API 來自三部分——prop、事件和插槽:html

  • prop 容許外部環境傳遞數據給組件
  • event 容許從組件內觸發外部環境的反作用
  • slot 容許外部環境將額外的內容組合在組件中

prop

組件具備自身狀態,當沒有相關 porps 傳入時,使用自身狀態完成渲染和交互邏輯;當該組件被調用時,若是有相關 props 傳入,那麼將會交出控制權,由父組件控制其行爲前端

僅一個值傳入組件

  • 若是該組件設計上支持雙向綁定,可以使用v-model將該參數傳入組件,減小記憶成本(畢竟 vue 官方的語法糖,不用白不用)
<my-component v-model="foo" />
複製代碼
  • 若是該組件能夠獨立運行,不依賴父組件時,仍是給這個值起個名字吧
<component-no-sync :childNeed="foo" />
複製代碼

不少值須要傳入組件

好比當一個組件有諸多配置項,且當沒有傳入配置項取用組件內部默認項的時候,咱們原先的父組件寫法:vue

<child-component :prop1="var1" :prop2="var2" :prop="var3" ... />
複製代碼

其實能夠在父組件上直接使用v-bind={子組件props集合}git

可是爲了方便覆寫子組件的內部配置項,不妨使用一個對象將配置收集到一塊兒,可是這種作法不能利用 props 驗證對象裏面每一個的值類型github

<child-component v-model="text" :setting="{color:'bule'}" />

// 子組件內部讀取配置,經過擴展運算符替換掉默認配置
const setting ={
  ...defaultSetting,
  ...this.setting
}
複製代碼

computed 屬性

vue 的 computed 屬性默認是隻讀的,你能夠提供一個 setter。它能夠優化我寫組件的邏輯,適用於父組件處理的值和子組件處理的值是同一個的狀況web

<template>
  <el-select v-model="email">
    <el-option v-for="item in adminUserOptions" :key="item.email" :label="item.email" :value="item.email" />
  </el-select>
</template>
複製代碼
export default {
  props: {
    value: {}
  },
  computed: {
    email: {
      get() {
        return this.value
      },
      set(val) {
        this.$emit('input', val)
        this.$emit('change', val)
      }
    }
  }
}
複製代碼

靈活的 prop

咱們常看到一些優秀的組件庫,傳入的值既能夠是一個 String/Number,也能夠是一個函數。element-ui

好比ElementUITable組件,當你想要顯示樹形數據的時候,必須傳入row-key。看它的介紹就知道是有多靈活:微信

row-key的做用:行數據的 Key,用來優化 Table 的渲染;在使用 reserve-selection 功能與顯示樹形數據時,該屬性是必填的。類型爲 String 時,支持多層訪問:user.info.id,但不支持 user.info[0].id,此種狀況請使用 Function數據結構

處理 rowKey 生成 RowIdentity 的函數源碼:

//https://github.com/ElemeFE/element/blob/dev/packages/table/src/util.js
export const getRowIdentity = (row, rowKey) => {
  if (!row) throw new Error('row is required when get row identity')
  // 行數據的key
  if (typeof rowKey === 'string') {
    if (rowKey.indexOf('.') < 0) {
      return row[rowKey]
    }
    // 支持多層訪問:user.info.id
    let key = rowKey.split('.')
    let current = row
    for (let i = 0; i < key.length; i++) {
      current = current[key[i]]
    }
    return current
    // 經過函數自定義
    // 我處理過父和子id可能相同的狀況,只好經過Function自定義
    // 不能夠經過時間或者隨機字符串生成ID
  } else if (typeof rowKey === 'function') {
    return rowKey.call(null, row)
  }
}
複製代碼

因爲業務場景多變,組件的設計者很難考慮徹底,不妨設計靈活的 prop,由開發者自行定義

其餘

當組件有 prop 傳入的時候,儘可能考慮一下,當 prop 變化的時候,組件是否可以響應 prop 的變化。也就是說使用 prop,是否使用了計算屬性,或者 watch 了 props 的值。

事件

emit/on

讀者確定知道 emit/on 如何使用,我就簡單說一下 vue 的 v-modelsync的語法糖,咱們能夠利用這些語法糖,幫助咱們寫出簡潔的代碼(父組件能夠少寫監聽子組件的事件,好比你不用寫@input)

v-model

看一下下面的代碼示例,就能懂這句話了。v-model 會忽略全部表單元素的 value、checked、selected 特性的初始值而老是將 Vue 實例的數據做爲數據來源。你應該經過 JavaScript 在組件的 data 選項中聲明初始值

<input v-model="searchText" />

<input v-bind:value="searchText" v-on:input="searchText = $event.target.value" />

// 當把v-model用在組件上

<custom-input v-bind:value="searchText" v-on:input="searchText = $event" ></custom-input>
複製代碼

爲了讓它正常工做,這個組件內的 <input> 必須:將其 value 特性綁定到一個名叫 value 的 prop 上在其 input 事件被觸發時,將新的值經過自定義的 input 事件拋出,即this.$emit('input',changedValue)

自定義 v-model

爲啥要自定義組件的 v-model 呢,由於數據不符合要求唄。你的輸入值不可能老是 value ,你的事件不可能老是 input,具體詳見文檔

sync(雙向綁定語法糖)

vue 真的是方便了開發者不少,站在開發者的角度考慮,很大的提高開發效率

以  update:myPropName  的模式觸發事件取代雙向綁定this.$emit('update:title', newTitle),具體詳見文檔

Function 經過 prop 傳入

原本想放在 prop 部分的,可是我的以爲其實它和 emit/on 更有關係一點

有讀者可能會問,爲何不能把子組件裏面的事件 emit 出來,經過父組件處理?而後傳入一個控制子組件的 prop 屬性。

我想說的是,能夠,可是這樣真的很麻煩,子組件內部的狀態卻要依賴父組件傳值。

該組件內部的狀態,咱們須要把它暴露出來嘛?我以爲不須要,組件內部的狀態就讓它處於組件內部

可是能夠經過傳入 function(你能夠理解爲一個鉤子),參與組件狀態變動的行爲。好比很好用的拖拽庫,Vue.Draggable控制元素是否被拖動的行爲。

Vue.Draggable能夠傳入一個 move 方法,咱們看一下它如何處理的。

onDragMove(evt, originalEvent) {
      const onMove = this.move;
      // 若是沒有傳入move,那麼返回true,能夠移動
      if (!onMove || !this.realList) {
        return true;
      }

      const relatedContext = this.getRelatedContextFromMoveEvent(evt);
      const draggedContext = this.context;
      const futureIndex = this.computeFutureIndex(relatedContext, evt);
      Object.assign(draggedContext, { futureIndex });
      const sendEvt = Object.assign({}, evt, {
        relatedContext,
        draggedContext
      });
      // 組件行爲由傳入的move函數控制
      return onMove(sendEvt, originalEvent);
}
複製代碼

這樣作的好處,就是組件內部自由一套運行邏輯,可是我能夠經過傳入 function 來干預。我沒有直接修改組件內部狀態,而是經過函數(你能夠稱它爲鉤子)去觸發,方便調試組件,使得組件行爲具備可預測性

父組件直接操做子組件

不多有這樣的騷操做,可是因爲數據和操做的複雜性,當數據結構複雜,嵌套過深的狀況下,父組件很難對於子組件的數據的精細控制

所以,若是不得已而爲之,請在文檔裏,把子組件能夠調用的方法暴露出來,供使用者使用。使用這種組件比較麻煩,得去看文檔,沒有文檔的只好去看源碼

ElementUItree組件提供了不少方法,用於父組件去操做子組件。

eg:this.$refs.tree.setCheckedKeys([]);

插槽

HTML <slot> element 是 Web Components 技術的一部分,是自定義 web 組件的佔位符,vue 裏面的 slot 的靈感來自 Web Components 規範草案,具體見文檔

默認插槽

能用默認插槽就不要使用具名插槽,我真的不想使用你這個組件的時候還去翻看你的插槽叫什麼名字

以前我司一個網頁模板 三個插槽,header,body,footer,我用的是真的難受,每次都記不得,看似三個單詞都挺熟悉的,可是其實 head,content,foot 這些單詞也都行啊,誰知道用啥(可能我老了吧,組件若是不是必要儘可能不要讓人有記憶成本)。

後備內容

就是給組件裏面的插槽定義默認值,它只會在沒有提供內容的時候被渲染。建議用上插槽就給它添加默認內容

封裝他人組件

有些時候咱們多是對他人的組件進行封裝,這裏強烈推薦使用v-bind="$attrs" 和 v-on="$listeners"vm.$attrs 是一個屬性,其包含了父做用域中不做爲 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外)。這些未識別的屬性能夠經過 v-bind="$attrs" 傳入內部組件。未識別的事件可經過v-on="$listeners"傳入

舉個例子,好比我建立了個人按鈕組件myButton,封裝了 element-ui 的 el-button 組件(其實什麼事情都沒作),在使用組件 <my-button />時,就能夠直接在組件上使用 el-button 的屬性,不被 prop 識別的屬性會傳入到 el-button 元素上去

<template>
  <div>
    <el-button v-bind="$attrs">導出</el-button>
  <div>
</template>
// 父組件使用
<my-button type='primary' size='mini'/>
複製代碼

組件命名

這裏推薦遵循 vue 官方指南,值得一看

咱們構建組件的時候一般會將其入口命名爲 index.vue ,引入的時候,直接引入該組件的文件夾便可。

可是這樣作會有一個問題,當你編輯多個組件的時候,全部的組件入口都叫作index.vue,容易糊塗

vscode 顯然意識到了這個問題,因此當文件名相同的文件被打開時,它會在文件名旁邊顯示文件夾名

如何解決呢,咱們能夠把 index.js 看成一個單純的入口,不承擔任何邏輯。僅僅負責引入component-name-container以及export default component-name-container

my-app
└── src
        └── components
                └── component-name
                    ├── component-name.css
                    ├── component-name-container.vue
                    └── index.js
複製代碼

tips(我的喜愛)

  • template,把一個<template> 元素當作不可見的包裹元素,並在上面使用 v-if。最終的渲染結果將不包含 <template> 元素
  • 能用 computed 計算屬性的,儘可能就不用 watch
  • 模板裏面寫太多 v-if 會讓你的模板很難看,v-else-if儘可能仍是別用了吧。一長串的 if else,在模板裏面看的很亂

關於我

一個一年小前端,關注個人微信公衆號,和我一塊兒交流,我會盡我所能,而且看看我能成長成什麼樣子吧。

微信公共號


你有什麼寫組件的獨特技巧,不妨在評論區告訴我吧

相關文章
相關標籤/搜索