基於Vue構造器建立Form組件的通用解決方案

在前端日常的業務中,不管是官網、展現頁仍是後臺運營系統都離不開表單,它承載了大部分的數據採集工做。因此如何更好地實現它,是日常工做中的一個重要問題。前端

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

<template>
  <header></header>
  ...
  <content></content>
  ...
  <footer></footer>
</template>
複製代碼

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

<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方便通訊。可是也會有如下幾個缺陷:github

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

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

實例

演示地址:演示實例bash

代碼地址:FatGe githubapp

  • 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`)
      })
    }
  }
}
</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()
      }
    },
    destroyElement () {
      this.$destroy()
    },
    close () {
      this.isVisible = false
    }
  },
  
  mounted () {
    document.body.appendChild(this.$el)
  },
    
  destroyed () {
    this.$el.parentNode.removeChild(this.$el)
  }
}
</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
  }
}
複製代碼

首先利用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()
  }   
},
destroyed () {
  this.$el.parentNode.removeChild(this.$el)
}
複製代碼

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

watch: {
  isVisible (newValue) {
    if (!newValue) {
      this.destroyElement()
      // 若是須要添加過渡動畫
      // this.$el.addEventListener('transitionend', this.destroyElement)
    }
  }
}
複製代碼

通常Form組件有兩個功能:

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

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

done && inlineListen({
  method: 'done',
  options,
  instance
})
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))
    })
  }
}
複製代碼

使用

能夠將上述屬於<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
    }
  }
}
複製代碼

再經過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)
        }
      }
    }
  }
}
</script>
複製代碼

調用時,只需

export default {
  name: 'App',
  methods: {
    register: transform(register),

    handleClick () {
      this.register({
        propsData: {
          ...
        },
        // done: data => function
        done () {
            // 外部關閉
            this.close()
        }
      })
    }
  }
}
複製代碼

PS:若是業務場景須要,在外部控制表單的關閉時,只須要改變done functioncontext,也就是this指針指向<Form/>

總結

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

Vue.prototype.method = transform(Toast-Component) 
複製代碼

原創聲明: 該文章爲原創文章,轉載請註明出處。

相關文章
相關標籤/搜索