設計模式在vue中的應用(六)
為什麼要寫這些文章呢。正如 設計模式(Design Pattern)是一套被反覆使用、多數人知曉的、經過分類的、程式碼設計經驗的總結 (來自百度百科)一樣,也是想通過分享一些工作中的積累與大家探討設計模式的魅力所在。
在這個系列文章中為了輔助說明引入的應用場景都是工作中真實的應用場景,當然無法覆蓋全面,但以此類推也覆蓋到了常見的業務場景
觀察者模式對我們來說應該不陌生,對vue原理稍微有點了解的都知道通過Object.defineProperty 攔截資料的 get/set ,在set中收集依賴Watcher,在get中觸發更新Watcher.notify(),這裡就是觀察者模式的應用
觀察者(Observer)模式與釋出(Publish)/訂閱(Subscribe)的關係
他們的行為方式相似add,notify/trigger,最明顯的區別是觀察者add一個具體觀察者物件,釋出/訂閱add一個訂閱事件
// 觀察者 class Observer { update () {} } const Observer1 = new Observer() Subject.add(Observer1) Subject.notify() // 釋出訂閱 Publish.add('event1',function Subscribe() { ...do something 1 }) Publish.add('event1',function Subscribe() { ...do something 2 }) Publish.add('event1',function Subscribe() { ...do something 3 }) Publist.trigger('event1') 複製程式碼
一、場景

- 兩個form表單——發票資訊和郵寄資訊
- 郵寄資訊表單只在選中增值稅專用發票時才需要
- 提交按鈕需在所有存在的表單(一個或者兩個)驗證通過後才有效,也就是在點選提交按鈕後獲取表單的驗證結果和輸入框的值
二、分析
大多數情況我們遇到的場景是一個表單對應一個提交按鈕,現在的場景是一個提交按鈕控制 N 個(大於1)且數量不可控的表單,我們可以藉助觀察者模式解決這個問題
將表單物件(元件)作為觀察者,點選提交按鈕notify所有觀察者(表單)獲取值
三、設計
// invoiceForm.vue <template> ... do something </template> <srcipt> export default { methods: { // 所有form元件提供統一的獲取值介面 getValue () { // 需返回一個promise,目前大多數vue表單驗證都是非同步的 return new Promise(() => { // 表單驗證,返回值 ...do something }) } } } </script> 複製程式碼
// postForm.vue <template> ... do something </template> <srcipt> export default { methods: { // 統一的獲取值介面 getValue () { // 需返回一個promise return new Promise(() => { ...do something }) } } } </script> 複製程式碼
// stage.vue <template> <div> <!-- ref獲取元件物件 --> <invoice-form ref="invoice" /> <post-form v-if="isNeedPost" ref="post" /> <button @click="handleSubmit">提交</button> </div> </template> <script> export default { data () { isNeedPost: true } methods: { async handleSubmit () { // 已知有invoice和post兩個觀察者 let invoice = await this.$refs.invoice.getValue() let post = {} if (this.$refs.post) { post = await this.$refs.post.getValue() } this.$axios.post({ invoice, post }) } } } </script> 複製程式碼
四、更復雜的場景

每次點選新增聯絡人按鈕頁面上會增加一個這樣的form,點選儲存按鈕時所有聯絡人表單分別做驗證,所有表單驗證通過後一併提交。與上面的場景類似,大家可以嘗試本文的思路。
上面的場景是已知有invoiceForm
、 postForm
,而這個場景下聯絡人form可以無限制的新增
當 ref 和 v-for 一起使用的時候,你得到的引用將會是一個包含了對應資料來源的這些子元件的陣列(來自vue文件)
設計實現
<template> <div> <div> <contact-form v-for="(item, index) in contactGroup" ref="contacts" :key="index" /> </div> <button @click="handleAdd">新增聯絡人</button> <button @click="handleSave">儲存</button> </div> </template> <script> export default{ data () { return { contactGroup: [true] } }, methods: { handleAdd () { this.contactGroup.push(true) }, async handleSave () { // 虛擬碼 try { // this.$refs.contacts是一個數組 const promises = this.$refs.contacts.map(contact => contact.getValue()) const contacts = await Promise.all(promises) this.$axios.post({ contacts }) } catch (error) { // 表單驗證不通過 console.dir(error) } } } } </script> 複製程式碼
結尾
本文的內容相對來說比較簡單,對於上面例舉的場景你也可能會有更好的解決方式,不過個人覺得這種需求場景對於理解觀察者模式還是蠻不錯的。
寫到這一篇覺得有必要搞個小總結: 1,在這一篇為了使用觀察者模式我們需要獲取觀察物件(元件)——ref 2,本系列第一篇我們用工廠模式生產input元件,為了透傳type、status工廠元件設計的也需要接受 這兩個props,如果這時要透傳更多的prop並且還有事件那怎麼辦呢,最好的方案——$attrs、$listeners 3,設計模式的一個主要特效能夠動態的組合和決定使用哪個物件(元件)——動態元件component 4,本系列第四篇為能自定義一個演算法(元件渲染)的某一步——slot 相信大家都已經瞭解了一個框架API的設計背後都是有一定考量的,當然上面例舉的4個都只是基於本系列 的內容不代表全部 有人說不會這樣用是因為對vue的文件不熟悉,我身邊有朋友沒事就把vue文件從頭看到尾,他是不熟? 個人覺得應該是程式碼的設計需求驅動你去深入瞭解框架,大多時候框架已經提供了滿足你要求的API直 接用就行了(查文件),如果框架沒有提供現成的能不能通過其它API hack一下(這個時候可能就需要深入 原始碼研究),如果依舊滿足不了你,在自己技術能力允許的情況下給框架原始碼提個PR? 複製程式碼
本文實現同樣適用於react,為什麼文章以vue做題?vue的template讓我們在理解一些概念的時候可能會有點不適應,而react的jsx可以看做就是在寫JavaScript對各種概念實現更靈活
友情提示:設計模式在vue中的應用應該會寫一個系列,喜歡的同學記得關注下