利用Vue構造器創建Form組件的通用解決方法

在前端平常的業務中,無論是官網、展示頁還是後臺運營系統都離不開表單,它承載了大部分的數據採集工作。所以如何更好地實現它,是平常工作中的一個重要問題。

在應用Vue框架去開發業務時,會將頁面上每個獨立的可視/可交互區域拆分爲一個組件,再通過多個組件的自由組合來組成新的頁面。例如

<template>
 <header></header>
 ...
 <content></content>
 ...
 <footer></footer>
</template>

當用戶的某個行爲觸發表單時(例如註冊、建立內容等),期望在頁面中彈出一個From組件。通常的做法是在template中填入一個<form>組件用於開發,並通過控制data中的UI.isOpen來對其display進行控制,例如在當前<template>組件內開發

<template>
 <header></header>
 ...
 <content></content>
 ...
 <footer></footer>
 ...
 <register-form v-if="UI.isOpen">
 <form-item></form-item>
 ...
 <submit-button></submit-button>
 </register-form>
</template>

Form組件與其父組件之間可以通過prop以及$emit方便通信。但是也會有以下幾個缺陷:

  • 當前組件的data必須要有UI.isOpen來控制表單,如果存在多個表單時,就會有大量的狀態來維護表單的開關;
  • 如果表單多次彈出時,可能需要對錶單的data進行重置;
  • 與組件化思想相違背,表單不屬於當前頁面,它只是由於用戶行爲觸發的結果。

爲了解決以上缺陷,並且還能具備方便通信的優勢,本文選擇用Vue.extend將原有<form>組件轉化爲method function,並維護在當前組件的method中,當用戶觸發時,在頁面中掛載,關閉時自動註銷。

實例

APP組件

<template>
 <div id="app">
 <el-button 
  type="primary" icon="el-icon-edit-outline"
  @click="handleClick"
 >註冊</el-button>
 </div>
</template>
 
<script>
import register from './components/register'
import { transform } from './transform'
 
export default {
 name: 'App',
 methods: {
 register: transform(register),
 
 handleClick () {
  this.register({
  propsData: { name: '皮鞋' },
  done: name => alert(`${name}牛B`)
  })//歡迎加入前端全棧開發交流圈一起吹水聊天學習交流:864305860
 }
 }
}
</script>

當<el-button>的點擊事件觸發時,調用register方法,將表單組件掛載在頁面中。

Form組件

<template>
 <div class="mock" v-if="isVisible">
 <div class="form-wrapper">
  <i class="el-icon-close close-btn" @click.stop="close"></i>
 
  ...<header />
  ...<content />
 
  <div class="footer">
   <el-button 
    type="primary"
    @click="handleClick"
   >確定</el-button>
 
   <el-button 
    type="primary"
    @click="handleClick"
   >取消</el-button>
  </div>
 </div>
 </div>
</template>
 
<script>
export default {
 porps: { ... },
 
 data () {
 return {
  isVisible: true
 }
 },
  
 watch: {
 isVisible (newValue) {
  if (!newValue) {
  this.destroyElement()
  }
 }
 },
  
 methods: {
 handleClick ({ type }) {
  const handler = {
  close: () => this.close()
  }//歡迎加入前端全棧開發交流圈一起吹水聊天學習交流:864305860
 },
 destroyElement () {
  this.$destroy()
 },
 close () {
  this.isVisible = false
 }
 },
  
 mounted () {
 document.body.appendChild(this.$el)
 },
  
 destroyed () {
 this.$el.parentNode.removeChild(this.$el)
 }//歡迎加入前端全棧開發交流圈一起學習交流:864305860
}
</script>

在APP組件內並未維護<form>組件的狀態,其打開或關閉只維護在自身的data中。

原理

上述代碼中,最爲關鍵的一步就是transform函數,它將原有的`從single-file components轉化爲了method function,其原理如下

const transform = (component) => {
 const _constructor = Vue.extend(component)
 return function (options = {}) {
 const {
  propsData
 } = options
 let instance = new _constructor({
  propsData
 }).$mount(document.createElement('div'))
 return instance
 }//歡迎加入前端全棧開發交流圈一起學習交流:864305860
}

首先利用Vue.extend(options)創建一個<Form/>組件的子類

const _constructor = Vue.extend(component)

然後return function,它的功能是:

  • 將<form />組件轉化爲method
  • 在method調用時,將組件實例化並傳遞propsData
const {
 propsData
} = options
let instance = new _constructor({
 propsData
}).$mount(document.createElement('div'))

爲了能夠控制實例化後的組件,選擇instance返回。 當組件實例化時,它只是掛載到document.createElement('div')上,但是並沒有掛載到頁面上,所以需要將其appendChild到頁面中。爲了更好的語義化,選擇在組件的生命週期中完成它在頁面中的掛載。實例化時,會觸發組件mounted生命週期,所以當其觸發時可以掛載在document.body中,具體如下

mounted () {
 document.body.appendChild(this.$el)
}

有了掛載,就必須要有註銷。對應的生命週期應該是destroyed,所以

method: {
 destroyElement () {
 this.$destroy()
 } //歡迎加入前端全棧開發交流圈一起學習交流:864305860
},
destroyed () {
 this.$el.parentNode.removeChild(this.$el)
}

組件註銷的時間與它在頁面中顯示息息相關,當<form />在頁面中不可見時候,需要註銷它

method: {
 destroyElement () {
 this.$destroy()
 } //歡迎加入前端全棧開發交流圈一起學習交流:864305860
},
destroyed () {
 this.$el.parentNode.removeChild(this.$el)
}

一般Form組件有兩個功能:

  • done:代表用戶確認;
  • cancel:代表用戶取消;

當done或cancel觸發時,APP組件內可能會有相應的變化,所以在組件實例化之後,利用$on去監聽對應的done事件以及cancel事件。

done && inlineListen({
 method: 'done',
 options,
 instance
})//歡迎加入前端全棧開發交流圈一起吹水聊天學習交流:864305860
cancel && inlineListen({
 method: 'cancel',
 options,
 instance
})

其中inlineListen函數可以方便後續添加其他的event,其代碼爲

const inlineListen = ({
 method,
 options,
 instance
}) => {
 let listener = `on${method}`
 instance[listener] = options[method]
 instance.$on(method, function (data) {
 this[listener](data)
 })
}

也可以將上述方案封裝成Promise形式,如下

export const transform = (component) => {
 const _constructor = Vue.extend(component)
 return function (options = {}) {
 const {
  propsData
 } = options
 
 return new Promise((resolve, reject) => {
  let instance = new _constructor({
  propsData
  }).$mount(document.createElement('div'))
 
  instance.$on('done', data => resolve(data))
 })//歡迎加入前端全棧開發交流圈一起學習交流:864305860
 }
}//幫助突破技術瓶頸,提升思維能力

使用

可以將上述屬於<Form/>公有的data以及method獨立出來,再通過mixins引入到每個表單內,例如

export default {
 data() {
 return {
  visible: true
 }
 },
 watch: {
 visible(newValue) {
  if (!newValue) {
  this.destroyElement()
  }
 }
 },
 mounted() {
 document.body.appendChild(this.$el)
 },
 destroyed() {
 this.$el.parentNode.removeChild(this.$el)
 },
 methods: {
 destroyElement() {
  this.$destroy()
 },
 close() {
  this.visible = false
 }//歡迎加入前端全棧開發交流圈一起學習交流:864305860
 }//面向1-3年前端人員
}//幫助突破技術瓶頸,提升思維能力

再通過mixins混入。

<script>
import popupWin from '../mixins/popup-win'
 
export default {
 mixins: [popupWin],
 
 data () {
 return {
  input: '',
  gender: 1
 }
 },
 methods: {
 handleClick ({ type }) {
  const handler = {
  close: () => this.close(),
  confirm: () => {
   const { input } = this
   this.$emit('done', input)
  }//歡迎加入前端全棧開發交流圈一起學習交流:864305860
  }
 }//面向1-3年前端人員
 }
}//幫助突破技術瓶頸,提升思維能力
</script>

調用時,只需

export default {
 name: 'App',
 methods: {
 register: transform(register),
 
 handleClick () {
  this.register({
  propsData: {
   ...
  },
  // done: data => function
  done () {
   // 外部關閉
   this.close()
  }//歡迎加入前端全棧開發交流圈一起學習交流:864305860
  })
 }//面向1-3年前端人員
 }
}//幫助突破技術瓶頸,提升思維能力

PS:如果業務場景需要,在外部控制表單的關閉時,只需要改變done function的context,也就是this指針指向<Form/>。

總結

通過上述的transform函數,將原有的注入式組件轉化爲了命令式,簡化了頁面狀態的維護,在通過mixins混入公有data以及method,簡化了表單組件開發。上述方法也可用於開發toast、alert、confirm等組件,只需要將

Vue.prototype.method = transform(Toast-Component)

結語

感謝您的觀看,如有不足之處,歡迎批評指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章