封裝vue元件的原則及技巧

語言: CN / TW / HK

封裝Vue元件的原則及技巧

Vue的元件系統

Vue元件的API主要包含三部分:prop、event、slot

  • props表示元件接收的引數,最好用物件的寫法,這樣可以針對每個屬性設定型別、預設值或自定義校驗屬性的值,此外還可以通過type、validator等方式對輸入進行驗證
  • slot可以給元件動態插入一些內容或元件,是實現高階元件的重要途徑;當需要多個插槽時,可以使用具名slot
  • event是子元件向父元件傳遞訊息的重要途徑

單向資料流

參考:單向資料流-官方文件。

父級 prop 的更新會向下流動到子元件中,但是反過來則不行

單向資料流是Vue元件一個非常明顯的特徵,不應該在子元件中直接修改props的值

  • 如果傳遞的prop僅僅用作展示,不涉及修改,則在模板中直接使用即可
  • 如果需要對prop的值進行轉化然後展示,則應該使用computed計算屬性
  • 如果prop的值用作初始化,應該定義一個子元件的data屬性並將prop作為其初始值

元件之間的通訊

這裡可以參考:vue元件通訊全揭祕,寫的比較全面

  • 父子元件的關係可以總結為prop 向下傳遞,事件event向上傳遞
  • 祖先元件和後代元件(跨多代)的資料傳遞,可以使用provide和inject來實現\ 此外,如果需要跨元件或者兄弟元件之間的通訊,可以通過eventBus或者vuex等方式來實現。

“繞開”單向資料流

考慮下面場景:父元件將資料通過prop形式傳遞給子元件,子元件進行相關操作並修改資料,需要修改父元件的prop值(一個典型的例子是:購物車的商品數量counter元件)。

根據元件單向資料流和和事件通訊機制,需要由子元件通過事件通知父元件,並在父元件中修改原始的prop資料,完成狀態的更新。在子元件中修改父元件的資料的場景在業務中也是比較常見的,那麼有什麼辦法可以“繞開”單向資料流的限制呢?

狀態提升

可以參考React的狀態提升,直接通過props將父元素的資料處理邏輯傳入子元件,子元件只做資料展示和事件掛載即可

```

1234567891011121314151617181920 ```

然後在呼叫時傳入事件處理函式

```

```

很明顯,由於在每個父元件中都需要實現on-minus和on-plus,因此狀態提升並沒有從根本上解決問題。

v-model語法糖

Vue內建了v-model指令,v-model 是一個語法糖,可以拆解為 props: value 和 events: input。就是說元件只要提供一個名為 value 的 prop,以及名為 input 的自定義事件,滿足這兩個條件,使用者就能在自定義元件上使用v-model

```

```

然後呼叫的時候只需要傳入v-model指令即可

<counter v-model="counerVal"/>

使用v-model,可以很方便地在子元件中同步父元件的資料。在2.2之後的版本中,可以定製v-model指令的prop和event名稱,參考model配置項

export default { model: { prop: 'value', event: 'input' }, // ... }

獲得元件例項的引用\ 在開發元件中,獲取元件例項是一個非常有用的方法。元件可以通過 $refs、$parents、$children等方式獲得vm例項引用

  • $refs在元件(或者dom上)增加ref屬性即可
  • $parents獲取子元件掛載的父元件節點
  • $children,獲取元件的所有子節點\ 這些介面返回的都是vnode,可以通過vnode.componentInstance獲得對應的元件例項,然後直接呼叫元件的方法或訪問資料。雖然這種方式多多少少有些違背元件的設計理念,增加了元件之間的耦合成本,但程式碼實現會更加簡潔。

表單驗證元件

通常情況下,表單驗證是表單提交前一個十分常見的應用場景。那麼,如何把表單驗證的功能封裝在元件內部呢?

下面是一個表單元件的示例,展示了通過獲得元件的引用來實現表單驗證功能。

首先定義元件的使用方式,

  • xm-form接收model和rule兩個prop

    • model表示表單繫結的資料物件,最後表單提交的就是這個物件
    • rule表示驗證規則策略,表單驗證可以使用async-validator外掛
  • xm-form-item接收的prop屬性,對應form元件的model和rule的某個key值,根據該key從model上取表單資料,從rule上取驗證規則\ 下面是使用示例程式碼

```

```

接下來讓我們實現form-item元件,其主要作用是放置表單元素,及展示錯誤資訊

```

```

然後讓我們來實現form元件

通過calcFormItems獲取每個xm-form-item的引用,儲存在formItems中\ 暴露validate介面,內部呼叫AsyncValidator,並根據結果遍歷formItems中每個表單元素的prop屬性,處理對應的error資訊

```

```

這樣我們就完成了一個通用的表單驗證元件。從這個例子中可以看出獲取元件引用,在元件開發中是一個非常有用的方法。

封裝API元件

一些元件如提示框、彈出框等,更適合單獨的API呼叫方式,如

import MessageBox from '@/components/MessageBox.vue MessageBox.toast('hello')

如何實現制這種不需要手動嵌入模板裡面的元件呢?原來,除了在通過在模板中嵌入元件到children掛載元件,Vue還為元件提供了手動掛載的方法$mount

let component = new MessageBox().$mount()document.getElementById('app').appendChild(component.$el)

通過這種方式,我們就是可以封裝API形式呼叫元件,下面是一個alert訊息提示的介面封裝

訊息彈窗元件

一個訊息元件就是在頁面指定繪製展示提示訊息的元件,下面是簡單實現

```

```

下面來實現訊息元件掛載到頁面的邏輯,並對外暴露展示訊息的介面

``` import Vue from 'vue';

// 具體的元件 import Alert from './alert.vue'; Alert.newInstance = properties => { const props = properties || {}; // 例項化一個元件,然後掛載到body上 const Instance = new Vue({ data: props, render (h) { return h(Alert, { props: props }); } }); const component = Instance.$mount(); document.body.appendChild(component.$el); // 通過閉包維護alert元件的引用 const alert = Instance.$children[0]; return { // Alert元件對外暴露的兩個方法 add (noticeProps) { alert.add(noticeProps); }, remove (name) { alert.remove(name); } } };

// 提示單例 let messageInstance; function getMessageInstance () { messageInstance = messageInstance || Alert.newInstance(); return messageInstance; } function notice({ duration = 1.5, content = '' }) { // 等待介面呼叫的時候再例項化元件,避免進入頁面就直接掛載到body上 let instance = getMessageInstance(); instance.add({ content: content, duration: duration }); }

// 對外暴露的方法 export default { info (options) { return notice(options); } } ```

然後就可以使用API的方式來呼叫彈窗元件了

``` import alert from './alert.js' // 直接使用 alert.info({content: '訊息提示', duration: 2}) // 或者掛載到Vue原型上 Vue.prototype.$Alert = alert // 然後在元件中使用 this.$Alert.info({content: '訊息提示', duration: 2})

```

高階元件

高階元件可以看做是函數語言程式設計中的組合。可以把高階元件看做是一個函式,他接收一個元件作為引數,並返回一個功能增強的元件。

高階元件是一個接替Mixin實現抽象元件公共功能的方法,不會因為元件的使用而汙染DOM(新增並不想要的div標籤等)、可以包裹任意的單一子元素等等

在React中高階元件是比較常用的元件封裝形式,在Vue中如何實現高階元件呢?

在元件的render函式中,只需要返回一個vNode資料型別即可,如果在render函式中提前做一些處理,並返回this.$slots.default[0]對應的vnode,就可以實現高階元件。

內建的keep-alive

Vue內建了一個高階元件keep-alive,檢視原始碼可以發現其實現原理,就是通過維護一個cache,並在render函式中根據key返回快取的vnode,來實現元件的持久化。

throttle

節流是web開發中處理事件比較常見的需求。常見的場景有及時搜尋框避免頻繁觸發搜尋介面、表單按鈕防止在短暫時間誤重複提交等

首先來看看Throttle元件的使用方式,接收兩個props

  • time表示節流的時間間隔
  • events表示需要處理的事件名,多個事件用逗號分隔\ 在下面的例子中,通過Throttle元件來控制其內部button的點選事件,此時連續點選多次,觸發clickBtn的次數要比點選的次數小(節流函式通過一個定時器進行處理)。

```

1234567 ```

下面是具體實現,實現高階元件的主要功能是在render函式中對當前插槽中的vnode進行處理

``` const throttle = function (fn, wait = 50, ctx) { let timer let lastCall = 0 return function (...params) { const now = new Date().getTime() if (now - lastCall < wait) return lastCall = now fn.apply(ctx, params) } }

export default { name: 'throttle', abstract: true, props: { time: Number, events: String, }, created() { this.eventKeys = this.events.split(',') this.originMap = {} this.throttledMap = {} }, // render函式直接返回slot的vnode,避免外層新增包裹元素 render(h) { const vnode = this.$slots.default[0] this.eventKeys.forEach((key) => { const target = vnode.data.on[key] if (target === this.originMap[key] && this.throttledMap[key]) { vnode.data.on[key] = this.throttledMap[key] } else if (target) { // 將原本的事件處理函式替換成throttle節流後的處理函式 this.originMap[key] = target this.throttledMap[key] = throttle(target, this.time, vnode) vnode.data.on[key] = this.throttledMap[key] } }) return vnode }, } ```